EP171-172. “admin-post.php:前台表单增删数据”
🔒 登录后可标记已读📌 说明:EP171 的 transcript 在「there's an extra column here with maybe a red x that you click on to delete that pet.」这句话处结束,EP172 开头是同一句话的重复接续——两者是同一段实操内容被切成两个文件,合并成一篇笔记。
📌 并入 EP170 的提醒:EP170 是 EP168 那条「getArgs() 要用 isset() 避免 PHP Warning」勘误的重复提醒,内容完全一致,已经记在 [[EP166-169_构建动态SQL查询-URL参数拼接WHERE条件]] 里,这里不重复贴代码。
给 /pet-adoption 页面加上「新增宠物」和「删除宠物」两个只有管理员能操作的表单,学习 WordPress 标准的前台表单提交处理方式:admin-post.php。表单提交到这个 WordPress 系统文件,配合表单里一个名叫 action 的隐藏字段的值,WordPress 会自动触发对应命名规则的 hook(admin_post_{action值} / admin_post_nopriv_{action值}),处理完在 hook 回调函数里用 wp_safe_redirect() 跳回原页面。这也是这门课插件开发第三章(数据库存储)的最后一讲。
涉及文件
wp-content/plugins/new-database-table/inc/template-pets.php(修改,新增表单)wp-content/plugins/new-database-table/new-database-table.php(修改,新增处理表单提交的 hook 和方法)
代码实现
inc/template-pets.php:表格里给管理员加「删除」列 + 底部新增宠物表单:
<table class="pet-adoption-table">
<tr>
<th>Name</th>
<th>Species</th>
<th>Weight</th>
<th>Birth Year</th>
<th>Hobby</th>
<th>Favorite Color</th>
<th>Favorite Food</th>
<?php if (current_user_can('administrator')) { ?>
<th>Delete</th>
<?php } ?>
</tr>
<?php
foreach($getPets->pets as $pet) { ?>
<tr>
<td><?php echo $pet->petname; ?></td>
<td><?php echo $pet->species; ?></td>
<td><?php echo $pet->petweight; ?></td>
<td><?php echo $pet->birthyear; ?></td>
<td><?php echo $pet->favhobby; ?></td>
<td><?php echo $pet->favcolor; ?></td>
<td><?php echo $pet->favfood; ?></td>
<?php if (current_user_can('administrator')) { ?>
<td style="text-align: center;">
<form action="<?php echo esc_url(admin_url('admin-post.php')) ?>" method="POST">
<input type="hidden" name="action" value="deletepet">
<input type="hidden" name="idtodelete" value="<?php echo $pet->id; ?>">
<button class="delete-pet-button">X</button>
</form>
</td>
<?php } ?>
</tr>
<?php }
?>
</table>
<?php
if (current_user_can('administrator')) { ?>
<form action="<?php echo esc_url(admin_url('admin-post.php')) ?>" class="create-pet-form" method="POST">
<p>Enter just the name for a new pet. Its species, weight, and other details with be randomly generated.</p>
<input type="hidden" name="action" value="createpet">
<input type="text" name="incomingpetname" placeholder="name...">
<button>Add Pet</button>
</form>
<?php }
?>
new-database-table.php:注册 hook + 新增/删除的处理方法:
function __construct() {
global $wpdb;
$this->charset = $wpdb->get_charset_collate();
$this->tablename = $wpdb->prefix . "pets";
add_action('activate_new-database-table/new-database-table.php', array($this, 'onActivate'));
add_action('admin_post_createpet', array($this, 'createPet'));
add_action('admin_post_nopriv_createpet', array($this, 'createPet'));
add_action('admin_post_deletepet', array($this, 'deletePet'));
add_action('admin_post_nopriv_deletepet', array($this, 'deletePet'));
add_action('wp_enqueue_scripts', array($this, 'loadAssets'));
add_filter('template_include', array($this, 'loadTemplate'), 99);
}
function deletePet() {
if (current_user_can('administrator')) {
$id = sanitize_text_field($_POST['idtodelete']);
global $wpdb;
$wpdb->delete($this->tablename, array('id' => $id));
wp_safe_redirect(site_url('/pet-adoption'));
} else {
wp_safe_redirect(site_url());
}
exit;
}
function createPet() {
if (current_user_can('administrator')) {
$pet = generatePet();
$pet['petname'] = sanitize_text_field($_POST['incomingpetname']);
global $wpdb;
$wpdb->insert($this->tablename, $pet);
wp_safe_redirect(site_url('/pet-adoption'));
} else {
wp_safe_redirect(site_url());
}
exit;
}
顺带的小改动:给「找到 X 条结果」的数字加千分位逗号:
<p>This page took <strong><?php echo timer_stop();?></strong> seconds to prepare. Found <strong><?php echo number_format($getPets->count); ?></strong> results (showing the first <?php echo count($getPets->pets) ?>).</p>
关键改动点:
current_user_can('administrator'):整个「新增」表单、以及表格里的「删除」列/按钮,都只对管理员显示——不是管理员的访客完全看不到这些控件,也无法提交表单- 表单提交目标统一是
admin_url('admin-post.php'):admin-post.php是 WordPress 内建的系统文件,专门设计给前台表单提交后处理用——很多人第一次看到这个文件名会本能地觉得「前台不该提交到 admin 相关的文件」,但这其实是 WordPress 官方推荐、完全预期会被这样使用的标准做法;用它的好处是处理逻辑可以统一写在插件的主 PHP 文件里,不需要在模板文件顶部堆砌一堆「检查某个变量是否已提交」的判断代码 esc_url(admin_url('admin-post.php')):admin_url()生成后台某个文件的完整 URL,esc_url()是标准的 URL 转义函数,输出到 HTML 属性里之前做一层安全处理<input type="hidden" name="action" value="...">是整套机制的关键:表单里必须有一个名字精确为action的隐藏字段,WordPress 会读取这个字段的值,自动触发名为admin_post_{这个值}的 hook——这里新增用的是createpet,删除用的是deletepet(都是全小写连写,没有下划线/驼峰,跟 hook 名字符串精确对应)admin_post_{action}与admin_post_nopriv_{action}要成对注册:admin_post_createpet只会在「用户已登录」(不管是不是管理员)时触发;如果访客完全没有登录,WordPress 改为触发admin_post_nopriv_createpet——这一对 hook 要同时注册、且指向同一个处理函数,因为函数体内部本来就会再检查一次current_user_can('administrator'),两种触发路径最终执行的还是同一套权限判断逻辑createPet():用之前就有的generatePet()辅助函数随机生成一整只假宠物的数据,再用表单提交上来的$_POST['incomingpetname'](经过sanitize_text_field()清理)覆盖掉里面的petname字段,最后$wpdb->insert($this->tablename, $pet)整个数组一次性插入deletePet():从$_POST['idtodelete'](同样先sanitize_text_field()清理)拿到要删除的宠物 ID,调用$wpdb->delete($表名, array('id' => $id))——$wpdb->delete()的第二个参数是一个「列名 => 值」的条件数组,效果等同于DELETE FROM 表 WHERE id = 值- 成功/失败都要重定向、并且用
wp_safe_redirect()而不是wp_redirect():wp_safe_redirect()会额外检查目标 URL 是否指向本站允许的域名,比普通的wp_redirect()多一层安全保障,官方建议只要是根据某些条件动态决定跳转目标(哪怕只是简单的 if/else 两个分支)都优先用这个安全版本 exit放在if/else判断结构外面、只写一次:不管是「有权限、操作完重定向」还是「没权限、重定向回首页」,两个分支最后都要执行重定向后紧跟exit(阻止 PHP 继续执行后面根本不需要的代码)——与其在每个分支各写一次exit,不如把它提到if/else结构外面统一收尾,减少重复- 删除按钮的表单里额外带一个隐藏字段存
idtodelete:因为要删除的是「当前这一行」的宠物,这个 ID 只有在渲染这一行的时候才知道,所以每一行都单独生成一个迷你表单、把这一行对应的 ID 存进隐藏字段里 number_format($getPets->count):PHP 内建函数,给大数字每三位加一个千分位逗号(比如100000显示成100,000),纯粹的展示细节优化
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
admin-post.php | WP 系统文件 | 官方推荐的前台表单提交统一处理入口 |
admin_post_{action值} | WP 动态 hook | 已登录用户提交带有 action={值} 隐藏字段的表单时触发 |
admin_post_nopriv_{action值} | WP 动态 hook | 未登录访客提交同样表单时触发,需跟上面那个成对注册 |
current_user_can($角色) | WP 内建 function | 判断当前用户是否具备指定角色/权限 |
$wpdb->delete($表名, $条件数组) | $wpdb 方法 | 按条件删除表中的记录 |
wp_safe_redirect($url) | WP 内建 function | 安全跳转,额外校验目标 URL 是否指向本站允许的域名 |
number_format($数字) | PHP 内建 function | 给数字加千分位分隔符,用于展示 |
常见坑
- 表单的隐藏字段名字拼错(不是精确的
action)——WordPress 不会识别,不会触发任何自定义 hook - 只注册
admin_post_{action},漏掉admin_post_nopriv_{action}——未登录的访客提交表单时会遇到 WordPress 默认报错,因为没有对应的 hook 处理这种情况 - 用
wp_redirect()而不是wp_safe_redirect()——少了一层「跳转目标是否为本站域名」的安全校验 - 重定向之后忘记调用
exit——PHP 会继续执行重定向语句之后的代码,可能导致重复输出或者不可预期的行为 - 处理函数内部不再次检查
current_user_can(),只依赖前端表单本身是否显示——只在模板里判断「是否管理员」来决定显不显示表单和按钮是不够的,真正的权限校验必须在服务器端的处理函数里再做一次,否则任何人都能绕过前端直接构造请求提交
[截图:管理员登录状态下的 /pet-adoption 页面,表格每行末尾多出的红色 X 删除按钮,以及页面底部的 Add Pet 新增宠物表单]
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 27, EP170, EP171, EP172