From 42362b655d59bf6f127469c0c3df42831876c4b2 Mon Sep 17 00:00:00 2001 From: IanM Date: Tue, 21 Apr 2026 07:38:50 +0100 Subject: [PATCH 1/2] test(nicknames): reproduce TypeError when min/max settings are empty strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UserResourceFields passes the raw settings value to Schema\Str::minLength(int) and maxLength(int). Numeric strings ('3', '150') coerce silently under PHP's default weak typing, but an empty string — the result of an admin clearing the min or max field in the nicknames config and saving — triggers: Str::maxLength(): Argument #1 ($length) must be of type int, string given From then on every forum request 500s because UserResourceFields is invoked during JSON:API resource resolution. Fresh installs are unaffected because the Extend\Settings() int defaults bypass the DB path. Test reproduces the exact production stack trace. Fix follows. --- .../tests/integration/api/EditUserTest.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/extensions/nicknames/tests/integration/api/EditUserTest.php b/extensions/nicknames/tests/integration/api/EditUserTest.php index 8760b71126..3f6666f82c 100644 --- a/extensions/nicknames/tests/integration/api/EditUserTest.php +++ b/extensions/nicknames/tests/integration/api/EditUserTest.php @@ -178,4 +178,45 @@ public static function nicknamesWithInjectionChars(): array 'html attribute inject' => ['">'], ]; } + + // Regression test for the TypeError raised by UserResourceFields when an + // admin saves the nicknames config form with an empty min or max field — + // the empty string is persisted to the settings table and then passed + // straight to Schema\Str::minLength(int)/maxLength(int), which rejects + // non-numeric strings under PHP's default type coercion rules. + // + // TypeError: Str::maxLength(): Argument #1 ($length) must be of type int, string given + // + // Numeric strings like '3' coerce silently, so the bug only surfaces when + // a field is cleared. The int defaults from the Settings extender mask it + // until the admin saves once. + #[Test] + public function request_succeeds_when_min_or_max_setting_is_empty_string(): void + { + $this->setting('flarum-nicknames.min', ''); + $this->setting('flarum-nicknames.max', ''); + + $this->prepareDatabase([ + 'group_permission' => [ + ['permission' => 'user.editOwnNickname', 'group_id' => Group::MEMBER_ID], + ], + ]); + + $response = $this->send( + $this->request('PATCH', '/api/users/2', [ + 'authenticatedAs' => 2, + 'json' => [ + 'data' => [ + 'type' => 'users', + 'attributes' => [ + 'nickname' => 'jane.smith', + ], + ], + ], + ]) + ); + + $this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents()); + $this->assertEquals('jane.smith', User::find(2)->nickname); + } } From d4901d5f24607effb72d3eec695654911af85040 Mon Sep 17 00:00:00 2001 From: IanM Date: Tue, 21 Apr 2026 08:01:16 +0100 Subject: [PATCH 2/2] fix(nicknames): handle non-int min/max length settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cast the persisted setting to int before handing it to Schema\Str's minLength/maxLength, and only apply the constraint when the admin has configured a positive value. An empty field saved from the admin panel was otherwise stored as '' — triggering a TypeError in PHP's default weak coercion — and, after the naive cast, coerced to 0, which made maxLength(0) reject every nickname. Reveals a gap in Schema\Str: the int-only signature offers no way to "disable" a length constraint via the condition argument without a guard at the call site. Keeping the guard local for this RC fix. --- extensions/nicknames/src/Api/UserResourceFields.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/extensions/nicknames/src/Api/UserResourceFields.php b/extensions/nicknames/src/Api/UserResourceFields.php index be06bf6795..7038437c7b 100644 --- a/extensions/nicknames/src/Api/UserResourceFields.php +++ b/extensions/nicknames/src/Api/UserResourceFields.php @@ -32,6 +32,13 @@ public function __invoke(): array $regex = "/$regex/"; } + // Settings are always returned as strings from the DB. Coerce and only + // apply the length constraint when the admin has configured a non-zero + // value; an empty field saved from the admin panel would otherwise be + // stored as '' and cast to 0, making maxLength(0) reject every value. + $min = (int) $this->settings->get('flarum-nicknames.min'); + $max = (int) $this->settings->get('flarum-nicknames.max'); + return [ Schema\Str::make('nickname') ->visible(false) @@ -43,8 +50,8 @@ public function __invoke(): array // may render as hyperlinks in notification emails. ->rule('not_regex:/[\[\]()<>]/') ->regex($regex ?? '', ! empty($regex)) - ->minLength($this->settings->get('flarum-nicknames.min')) - ->maxLength($this->settings->get('flarum-nicknames.max')) + ->minLength($min, $min > 0) + ->maxLength($max, $max > 0) ->unique('users', 'nickname', true, (bool) $this->settings->get('flarum-nicknames.unique')) ->unique('users', 'username', true, (bool) $this->settings->get('flarum-nicknames.unique')) ->validationMessages([