WP DEVELOP

EP127. “设置表单收尾:回显、复用 HTML 函数与自定义校验”

首页 WordPress 开发课程 插件开发 CH1:PHP 基础 · EP127
约 18 分钟· #EP127#插件开发 CH1:PHP 基础
🔒 登录后可标记已读

把上一讲的设置表单填完整:① 用 selected()/checked() 让下拉框/勾选框正确回显数据库里已保存的值;② 补齐剩下的「标题文字」输入框和三个勾选框(字数统计/字符统计/阅读时间),其中三个勾选框刻意写成一个可复用的函数(用参数区分具体是哪个字段),而不是复制三份几乎一样的代码;③ 学习自定义校验逻辑(sanitize_callback 换成自己写的函数),演示如果用户绕过前端下拉框、用浏览器开发者工具硬塞一个非法值,服务端也能拦截并保留旧值。


涉及文件

  • wp-content/plugins/our-first-unique-plugin/our-first-unique-plugin.php (修改)

代码实现

settings():补齐剩余 4 个字段的注册(标题文字 + 三个勾选框,勾选框共用同一个 HTML 回调函数,用第 6 个参数传入区分用的 theName):

function settings() {
  add_settings_section('wcp_first_section', null, null, 'word-count-settings-page');

  add_settings_field('wcp_location', 'Display Location', array($this, 'locationHTML'), 'word-count-settings-page', 'wcp_first_section');
  register_setting('wordcountplugin', 'wcp_location', array('sanitize_callback' => array($this, 'sanitizeLocation'), 'default' => '0'));

  add_settings_field('wcp_headline', 'Headline Text', array($this, 'headlineHTML'), 'word-count-settings-page', 'wcp_first_section');
  register_setting('wordcountplugin', 'wcp_headline', array('sanitize_callback' => 'sanitize_text_field', 'default' => 'Post Statistics'));

  add_settings_field('wcp_wordcount', 'Word Count', array($this, 'checkboxHTML'), 'word-count-settings-page', 'wcp_first_section', array('theName' => 'wcp_wordcount'));
  register_setting('wordcountplugin', 'wcp_wordcount', array('sanitize_callback' => 'sanitize_text_field', 'default' => '1'));

  add_settings_field('wcp_charactercount', 'Character Count', array($this, 'checkboxHTML'), 'word-count-settings-page', 'wcp_first_section', array('theName' => 'wcp_charactercount'));
  register_setting('wordcountplugin', 'wcp_charactercount', array('sanitize_callback' => 'sanitize_text_field', 'default' => '1'));

  add_settings_field('wcp_readtime', 'Read Time', array($this, 'checkboxHTML'), 'word-count-settings-page', 'wcp_first_section', array('theName' => 'wcp_readtime'));
  register_setting('wordcountplugin', 'wcp_readtime', array('sanitize_callback' => 'sanitize_text_field', 'default' => '1'));
}

下拉框回显(selected())+ 输入框回显(esc_attr() + get_option()

function headlineHTML() { ?>
  <input type="text" name="wcp_headline" value="<?php echo esc_attr(get_option('wcp_headline')) ?>">
<?php }

function locationHTML() { ?>
  <select name="wcp_location">
    <option value="0" <?php selected(get_option('wcp_location'), '0') ?>>Beginning of post</option>
    <option value="1" <?php selected(get_option('wcp_location'), '1') ?>>End of post</option>
  </select>
<?php }

可复用的勾选框 HTML 函数(接收一个 $args 数组,靠 theName 区分具体是哪个字段,避免写三份几乎相同的函数):

// reusable checkbox function
function checkboxHTML($args) { ?>
  <input type="checkbox" name="<?php echo $args['theName'] ?>" value="1" <?php checked(get_option($args['theName']), '1') ?>>
<?php }

自定义校验函数(把 sanitize_callback 从通用的 sanitize_text_field 换成自己的方法,拦截非法值):

function sanitizeLocation($input) {
  if ($input != '0' AND $input != '1') {
    add_settings_error('wcp_location', 'wcp_location_error', 'Display location must be either beginning or end.');
    return get_option('wcp_location');
  }
  return $input;
}

关键改动点:

  • selected($value1, $value2) / checked($value1, $value2) 是 WordPress 内建的一对辅助函数:两个值相等时分别输出 selected="selected" / checked="checked" 这个 HTML 属性,不相等就什么都不输出——不用自己写 if 判断再手动拼字符串
  • get_option($name) 可以在同一个请求里反复调用多次而不会有性能损耗——WordPress 会在首次请求时把 wp_options 表里所有标记为 autoload = yes 的选项一次性加载进内存缓存,之后同一请求内的 get_option() 调用都是读内存,不会重复查询数据库
  • 三个勾选框的差异只在字段名(name 属性和 get_option() 查询的键名),所以把这部分「唯一变化的字符串」抽成 $args['theName'] 参数,checkboxHTML($args) 就能被三次 add_settings_field() 共用——add_settings_field()第六个参数(数组)会被原样传给对应的 HTML 回调函数,属性名(这里是 theName)是自己随便取的,只要跟读取时的 key 对上就行
  • esc_attr() 包住 get_option() 的输出结果再塞进 value="..." 属性——即使这个页面只有管理员能看到,依然坚持「凡是把数据库内容输出到 HTML 属性就要转义」的安全习惯
  • 自定义校验函数 sanitizeLocation($input) 替换掉的是 register_setting() 第三个参数里的 sanitize_callback,写法从字符串 'sanitize_text_field' 换成 array($this, 'sanitizeLocation')(跟其他方法引用方式一致);WordPress 会把用户提交的原始值作为 $input 传进来,只要这个函数最终 return 了什么值,那就是最终会被存进数据库的值
  • 校验失败时的标准做法:add_settings_error($setting, $code, $message) 往页面顶部添加一条红色错误提示(三个参数依次是关联的设置名、这条错误自己的 ID、给用户看的文字),然后 return get_option('wcp_location')——返回数据库里原本的旧值,而不是用户刚提交的非法值,等于让这次非法提交完全不生效
  • 转录里演示了真实的攻击场景:用浏览器开发者工具直接把 <select> 元素的某个 <option>value 改成 500 再提交——证明「前端限制了下拉框只有两个选项」不能替代「后端也要做校验」,因为前端的 HTML 结构本身可以被访问者随意篡改后再提交

[截图:wp-admin Word Count 设置页面完整表单,含 Headline Text 输入框和三个已回显勾选状态的复选框]

[截图:用开发者工具篡改下拉框选项后提交,页面顶部出现的 "Display location must be either beginning or end." 错误提示]


Hook / Function 速查

名称类型用途
selected($a, $b)WP 内建 function两值相等时输出 selected="selected",用于 <option>
checked($a, $b)WP 内建 function两值相等时输出 checked="checked",用于 checkbox/radio
get_option($name)WP 内建 function读取指定名称的选项值,同一请求内多次调用不会重复查库
add_settings_error($setting, $code, $message, $type)WP 内建 function在设置页面顶部显示一条提示/错误信息

常见坑

  • 以为前端 <select>/<input> 限制了取值范围就足够安全——浏览器开发者工具能让任何人绕过前端 HTML 的限制直接提交任意值,服务端必须独立做一遍校验,不能只信任前端
  • 自定义校验函数校验失败时忘记 return 旧值——如果不 return 任何东西(或者返回了非法的新值),会导致数据库存入错误数据,或者报 PHP 相关的警告
  • 多个几乎相同的字段各写一份几乎重复的 HTML 回调函数——转录特意展示了「保留旧代码作为注释、换成参数化的复用函数」这个重构过程,值得作为「发现重复代码时如何重构」的参考

延伸 / 后续讲座会用到

表单和存储都已经完成,下一讲开始写插件真正的核心逻辑——读取这些设置,计算字数/字符数/预估阅读时间,拼进博客文章详情页。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 23, EP127