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:
2026-10-23 23:18:51 +08:00
parent 0799268661
commit 4beb09307c
2 changed files with 30 additions and 2 deletions
@@ -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'] ) ) {
/*
* ⚠️ 表单里没有这个键时,**必须保留库里已有的值**,不能写空。
*
* 原来这里直接写 `$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];
}
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
/*
Theme Name: Kratos_Yang
Text Domain: kratos_Yang
Version: 3.2.16
Version: 3.2.17
Requires PHP: 7.0
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好