From e7ffb44b4547bfb4d88ced3a920b9c82ac12365e Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 16 Sep 2026 14:12:40 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E5=85=BC=E5=AE=B9=E6=8F=90?= =?UTF-8?q?=E7=8E=B0=E9=87=91=E9=A2=9D=E5=8E=86=E5=8F=B2=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Admin/SchemaProvider.php | 48 +++++++++++++------ .../widget-system/admin-ui-contract.test.cjs | 12 +++++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/Admin/SchemaProvider.php b/src/Admin/SchemaProvider.php index 2d6f94df..ebdd5aa3 100644 --- a/src/Admin/SchemaProvider.php +++ b/src/Admin/SchemaProvider.php @@ -175,12 +175,7 @@ class SchemaProvider { continue; } if (($field['sanitize_type'] ?? '') === 'money_decimal') { - try { - if (!is_string($value)) { - throw new \InvalidArgumentException('money_decimal_must_be_string'); - } - \AKSRT\Ledger\AssetAmount::balance(trim($value)); - } catch (\InvalidArgumentException $e) { + if ($this->normalize_money_decimal($value) === '') { $errors[] = sprintf('%s must be a decimal string with at most two fractional digits', $field['label'] ?? $key); continue; } @@ -543,6 +538,12 @@ class SchemaProvider { if (array_key_exists($field['key'], $stored)) { $data[$field['key']] = $stored[$field['key']]; } + if (($field['sanitize_type'] ?? '') === 'money_decimal' && array_key_exists($field['key'], $data)) { + $normalized = $this->normalize_money_decimal($data[$field['key']]); + if ($normalized !== '') { + $data[$field['key']] = $normalized; + } + } $stored_value = array_key_exists($field['key'], $stored) ? $stored[$field['key']] : ($data[$field['key']] ?? ''); @@ -748,7 +749,31 @@ class SchemaProvider { return $fields; } - private function sanitize_value($value, string $type) { + /** + * Normalize legacy numeric money settings without accepting lossy precision. + * + * @param mixed $value + */ + private function normalize_money_decimal($value): string { + if (is_int($value)) { + $value = (string) $value; + } elseif (is_float($value)) { + if (!is_finite($value)) { + return ''; + } + $value = (string) $value; + } elseif (!is_string($value)) { + return ''; + } + + try { + return \AKSRT\Ledger\AssetAmount::balance(trim($value))->decimal(); + } catch (\InvalidArgumentException $e) { + return ''; + } + } + + private function sanitize_value($value, string $type) { switch ($type) { case 'attachment_ids': if (!is_array($value)) { @@ -771,14 +796,7 @@ class SchemaProvider { case 'float': return (float) $value; case 'money_decimal': - if (!is_string($value)) { - return ''; - } - try { - return \AKSRT\Ledger\AssetAmount::balance(trim($value))->decimal(); - } catch (\InvalidArgumentException $e) { - return ''; - } + return $this->normalize_money_decimal($value); case 'array': // 深层字符串净化,保留嵌套服务商配置(如 geetest.captcha_id) if (!is_array($value)) { diff --git a/tests/widget-system/admin-ui-contract.test.cjs b/tests/widget-system/admin-ui-contract.test.cjs index a2170c0c..11f744b3 100644 --- a/tests/widget-system/admin-ui-contract.test.cjs +++ b/tests/widget-system/admin-ui-contract.test.cjs @@ -11,6 +11,7 @@ const styles = read('assets/admin/src/style.css') const field = read('assets/admin/src/components/form/AksField.vue') const fieldGroup = read('assets/admin/src/components/form/AksFieldGroup.vue') const formRenderer = read('assets/admin/src/components/form/AksFormRenderer.vue') +const schemaProvider = read('src/Admin/SchemaProvider.php') test('admin theme exposes the approved white, grey, and green tokens', () => { const requiredTokens = { @@ -63,6 +64,17 @@ test('navigation, focus, responsive layout, and reduced motion are contracted', assert.match(styles, /@media \(prefers-reduced-motion:\s*reduce\)/) }) +test('money decimal settings normalize legacy numeric values before validation', () => { + assert.match(schemaProvider, /private function normalize_money_decimal\(\$value\): string/) + assert.match(schemaProvider, /if \(is_int\(\$value\)\)/) + assert.match(schemaProvider, /is_finite\(\$value\)/) + assert.match(schemaProvider, /AssetAmount::balance\(trim\(\$value\)\)->decimal\(\)/) + assert.match(schemaProvider, /sanitize_type.*money_decimal[\s\S]*?normalize_money_decimal\(\$data\[\$field\['key'\]\]\)/) + assert.match(schemaProvider, /sanitize_type.*money_decimal[\s\S]*?normalize_money_decimal\(\$value\)/) + assert.match(read('assets/admin/src/schema/sections/payment/withdrawal.js'), /affiliate_min_withdrawal: '50\.00'/) + assert.match(read('assets/admin/src/schema/sections/payment/withdrawal.js'), /affiliate_daily_withdrawal_limit: '500\.00'/) +}) + test('captcha scene whitelist matches both admin schemas and excludes dead email verification', () => { const captcha = read('inc/modules/captcha.php') const phpSchema = read('src/Admin/Schema/SchemaPermissions.php')