3.2.17: 修复「保存设置时静默清空未提交的字段」
现象
在主题设置里填了「附加日期」(例如 10-23),过一阵发现它变回空了,
于是当天的哀悼效果不再出现,而其他设置都好好的,看不出哪里坏了。
根因
inc/options-framework/includes/class-options-framework-admin.php 的
validate_options() 里,对每个选项无条件取 $input[$id] 去 sanitize:
$clean[$id] = apply_filters('of_sanitize_'.$type, $input[$id], $option);
一旦某次提交里没有这个键(分组没渲染、字段被条件隐藏、表单中途出错只
提交了一半……),$input[$id] 是未定义索引 ⇒ null ⇒ sanitize_text_field()
返回空串 ⇒ 把用户存了很久的配置写成空。而且"清成空串"和"用户主动清空"
在库里完全一样,事后无法区分,只表现为"我明明填过,怎么没了"。
实测证据(线上 PHP 8.5.12,同一套用例)
修复前:3 通过 / 2 失败,失败项同时伴随
PHP Warning: Undefined array key "g_rip_extra" ... line 252
修复后:5 通过 / 0 失败
改法
- 循环外先取一次 get_option('kratos') 作 $saved_options(不在循环里查库)
- 提交了(array_key_exists($id,$input))才 sanitize
- 没提交则沿用 $saved_options 里的旧值;旧值也没有就不写这个键,
让运行时的默认值继续生效
- checkbox / multicheck 保持原语义:这两类"没提交"确实等于"没勾",
沿用旧值会导致开关关不掉。用 $is_toggle 单独判定,与"沿用旧值"互斥
为什么这个改动值得单独发一版
它不是界面问题,是**数据被静默改写**。凡是这种"用户没动、值却变了"的
缺陷,都会让人怀疑数据不可靠;而且每次后台保存都可能再触发一次。
验证
- 线上 _test_keep_old_value.php:5/5(修复前 3/5,用例 2 精确复现空串)
- 本地 _static_317.php:8/8,第 4 组自带注入(改成老写法必须变红)
- 全量流水线 verify_all.sh:rc=0
- 线上浏览器 _live315.py:纪念日分支 33/33、非纪念日分支 27/27
(判据改为「由配置侧独立算 expect_show,再与页面渲染对比」,
不再无条件断言"必须有灰度",也不再拿页面自身当预期值)
This commit is contained in:
@@ -223,6 +223,11 @@ class Options_Framework_Admin {
|
||||
|
||||
$clean = array();
|
||||
$options = & Options_Framework::_optionsframework_options();
|
||||
// 已保存的值,供"字段未提交时沿用旧值"用。放循环外,避免重复查库。
|
||||
$saved_options = get_option( 'kratos' );
|
||||
if ( ! is_array( $saved_options ) ) {
|
||||
$saved_options = array();
|
||||
}
|
||||
foreach ( $options as $option ) {
|
||||
|
||||
if ( ! isset( $option['id'] ) ) {
|
||||
@@ -249,7 +254,30 @@ class Options_Framework_Admin {
|
||||
|
||||
// For a value to be submitted to database it must pass through a sanitization filter
|
||||
if ( has_filter( 'of_sanitize_' . $option['type'] ) ) {
|
||||
$clean[$id] = apply_filters( 'of_sanitize_' . $option['type'], $input[$id], $option );
|
||||
/*
|
||||
* ⚠️ 表单里没有这个键时,**必须保留库里已有的值**,不能写空。
|
||||
*
|
||||
* 原来这里直接写 `$input[$id]`。如果该字段没被提交(比如某个
|
||||
* 分组没渲染出来、或者字段被条件隐藏、或者表单中途出错只提交了一半),
|
||||
* `$input[$id]` 是未定义索引 ⇒ 取到 null ⇒
|
||||
* `sanitize_text_field(null)` 返回 `''` ⇒ **把用户存了很久的
|
||||
* 配置静默清成空串**。而"清成空串"和"用户主动清空"在库里长得一模一样,
|
||||
* 事后完全无法区分,只会表现为"我明明填过,怎么没了"。
|
||||
*
|
||||
* 例外:checkbox 和 multicheck 上面已经把缺省项补成了 false,
|
||||
* 这两类的"没提交"在语义上确实等于"没勾",保持原样。
|
||||
*/
|
||||
$is_toggle = in_array( $option['type'], array( 'checkbox', 'multicheck' ), true );
|
||||
if ( array_key_exists( $id, $input ) ) {
|
||||
$clean[$id] = apply_filters( 'of_sanitize_' . $option['type'], $input[$id], $option );
|
||||
} elseif ( $is_toggle ) {
|
||||
$clean[$id] = apply_filters( 'of_sanitize_' . $option['type'], $input[$id], $option );
|
||||
} else {
|
||||
// 没提交 ⇒ 沿用旧值(旧值也没有就不写这个键,让运行时默认值生效)
|
||||
if ( array_key_exists( $id, $saved_options ) ) {
|
||||
$clean[$id] = $saved_options[$id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user