EP133. “手动处理表单提交:存取数据与 Nonce 安全校验”
🔒 登录后可标记已读📌 并入 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 速查
| 名称 | 类型 | 用途 |
|---|---|---|
$_POST | PHP 超全局变量 | 存放当前请求通过 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 提醒并入)