WP DEVELOP

EP133. “手动处理表单提交:存取数据与 Nonce 安全校验”

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

📌 并入 EP132 的提醒:本讲判断表单是否刚提交时用的写法 $_POST['justsubmitted'] == "true",在较新版本的 PHP 上会报 Warning: Undefined array key "justsubmitted"(因为页面首次加载、还没提交过表单时,$_POST 数组里根本没有这个键)。解决方法是外面包一层 isset()if (isset($_POST['justsubmitted']) == "true") $this->handleForm()。这是这门课里第二次遇到同类问题(第一次是很早以前 pageBanner() 函数),提醒但不强制立即修改,后续如果看到这条警告可以照此调整。

这一讲不使用 Settings API,完全手写表单提交处理逻辑:用一个隐藏字段判断「这次加载是不是表单刚提交后触发的」,用 update_option()/get_option() 存取数据,最后加上 Nonce 校验防止跨站请求伪造(CSRF)攻击,并检查用户权限。


涉及文件

  • wp-content/plugins/our-word-filter-plugin/index.php (修改)

代码实现

wordFilterPage():用隐藏字段判断表单是否刚提交,触发处理逻辑;文本域回显数据库里的值

function wordFilterPage() { ?>
  <div class="wrap">
    <h1>Word Filter</h1>
    <?php if ($_POST['justsubmitted'] == "true") $this->handleForm() ?>
    <form method="POST">
      <input type="hidden" name="justsubmitted" value="true">
      <?php wp_nonce_field('saveFilterWords', 'ourNonce') ?>
      <label for="plugin_words_to_filter"><p>Enter a <strong>comma-separated</strong> list of words to filter from your site's content.</p></label>
      <div class="word-filter__flex-container">
        <textarea name="plugin_words_to_filter" id="plugin_words_to_filter" placeholder="bad, mean, awful, horrible"><?php echo esc_textarea(get_option('plugin_words_to_filter')) ?></textarea>
      </div>
      <input type="submit" name="submit" id="submit" class="button button-primary" value="Save Changes">
    </form>
  </div>
<?php }

handleForm():校验 Nonce + 权限,通过才真正保存数据

function handleForm() {
  if (wp_verify_nonce($_POST['ourNonce'], 'saveFilterWords') AND current_user_can('manage_options')) {
    update_option('plugin_words_to_filter', sanitize_text_field($_POST['plugin_words_to_filter'])); ?>
    <div class="updated">
      <p>Your filtered words were saved.</p>
    </div>
  <?php } else { ?>
    <div class="error">
      <p>Sorry, you do not have permission to perform that action.</p>
    </div>
  <?php }
}

关键改动点:

  • 表单没有 action 属性,默认提交给「当前页面自己」——这意味着提交后还是执行同一个 wordFilterPage() 方法,靠隐藏字段 justsubmitted 判断这次加载是「首次访问」还是「刚提交完表单后的重新加载」
  • $_POST 是 PHP 原生的超全局数组,装着这次请求通过 POST 方式提交上来的全部表单数据,不是 WordPress 专属的东西
  • handleForm() 内部先用 wp_verify_nonce($_POST['ourNonce'], 'saveFilterWords') 校验安全令牌是否有效——两个参数分别是提交上来的 nonce 值、和生成这个 nonce 时用的「动作名」(必须跟 wp_nonce_field('saveFilterWords', 'ourNonce') 里的第一个参数完全一致),再加 current_user_can('manage_options') 确认当前用户确实有权限——两个条件同时满足才真正调用 update_option() 写数据库,否则显示错误提示且不保存
  • wp_nonce_field($action, $name) 输出的是一个隐藏的 <input>,值是 WordPress 生成的一次性令牌;这跟之前 REST API 场景里用的 wp_create_nonce() + 请求头传递本质是同一套机制,只是这里是「传统表单」场景,直接嵌进表单当隐藏字段,不需要自己手动加请求头
  • CSRF(跨站请求伪造)攻击的核心风险:如果没有 nonce 校验,理论上恶意网站可以诱导一个已登录你后台的管理员,在毫不知情的情况下点击一个链接/按钮,让浏览器悄悄向你的网站发一个伪造的提交请求。Nonce 能确保「这个提交请求确实来自你自己网站上的这个表单页面」,而不是外部伪造的
  • update_option($name, $value) / get_option($name) 是操作 wp_options 表最基础的一对函数,不需要自己写 SQL;写入前依然要 sanitize_text_field() 净化,读出后要用 esc_textarea()(而不是 esc_attr())转义再输出到 <textarea> 标签内部——这是这门课第一次强调 <textarea> 内容要用专门的 esc_textarea(),跟输出到属性值(esc_attr())或普通 HTML 正文(esc_html())的场景要用不同的转义函数

[截图:wp-admin Words List 页面提交表单后,出现的 "Your filtered words were saved." 绿色成功提示]


Hook / Function 速查

名称类型用途
$_POSTPHP 超全局变量存放当前请求通过 POST 方式提交的表单数据
update_option($name, $value)WP 内建 function写入/更新一个选项值到 wp_options
wp_nonce_field($action, $name)WP 内建 function输出包含 nonce 令牌的隐藏表单字段
wp_verify_nonce($nonce, $action)WP 内建 function校验提交上来的 nonce 是否有效、是否对应指定的动作名
current_user_can($capability)WP 内建 function判断当前用户是否拥有指定权限能力
esc_textarea()WP 内建 function专门用于输出到 <textarea> 内容的转义函数

常见坑

  • 判断「表单是否刚提交」时直接读 $_POST['justsubmitted'] 不做 isset() 判断——首次访问页面时这个键根本不存在,在较新 PHP 版本上会报未定义数组键的警告(EP132 已提前提醒)
  • 只用 nonce 校验、不检查用户权限(反之亦然)——两者解决的是不同层面的问题:nonce 防的是「请求是否真的来自这个表单页面本身」,权限检查防的是「即使请求合法,这个用户是否真的有资格执行这个操作」,两个检查都不能省
  • <textarea> 里的动态内容用 esc_attr() 而不是 esc_textarea()——两者转义策略不完全相同,esc_textarea() 是专门为 <textarea> 场景优化的函数,应该按输出场景选用对应的转义函数

延伸 / 后续讲座会用到

数据的存取和安全校验都已经完成,下一讲要写插件真正的核心功能:拿这份逗号分隔的敏感词列表,去过滤博客文章正文里出现的这些词。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 23, EP133(含 EP132 提醒并入)