WordPress自定义文章类型子菜单页数据无法保存至options表
问题原因
挂载到自定义文章类型子菜单时设置保存失效,是代码中多个核心错误叠加导致,和挂载位置本身无关:
- 页面load钩子名称错误:子菜单挂载到不同父级菜单时,WordPress生成的页面触发钩子名规则不同。参考教程将子菜单放在Tools菜单下,对应钩子规则和自定义文章类型子菜单不一致,原钩子根本不会触发,保存逻辑完全没有执行。
- Nonce校验参数不匹配:表单输出的nonce字段、动作名,和保存逻辑中校验用的参数完全不一致,就算钩子触发,校验也会直接失败拦截保存。
- 选项键名拼写不一致:部分字段
update_option和delete_option用的键名不统一,就算保存成功也无法正常读取、删除。 - 额外安全与前端bug:未对用户提交的输入做安全清理、两个文本域ID重复、表单提交地址未明确指定、输出字段值未做转义,存在安全风险和前端兼容问题。
修复步骤
1. 修正保存逻辑绑定的load钩子名
自定义文章类型的列表页父路径为edit.php?post_type=xxx,对应子菜单页面的load钩子格式为load-edit.php_page_{子菜单slug},替换原钩子绑定代码:
// 替换原 add_action( 'load-alumns-settings', 'alumns_reps_save_settings' ); add_action( 'load-edit.php_page_alumns-settings', 'alumns_reps_save_settings' );
2. 修正保存逻辑代码
统一Nonce校验参数,增加输入安全清理,修正错误键名,增加保存后跳转避免重复提交:
function alumns_reps_save_settings() { // 替换原$action和$nonce值,和表单wp_nonce_field输出的参数完全匹配 $action = 'alumns-reps-settings-page-save'; $nonce = 'alumns-reps-settings-page-save-nonce'; if ( ! alums_reps_user_can_save( $action, $nonce ) ) { return; } // 所有字段保存逻辑增加输入安全清理,修正键名错误 if ( isset( $_POST['state-reps-headding'] ) ) { $main_title = sanitize_text_field( wp_unslash( $_POST['state-reps-headding'] ) ); update_option( 'state-reps-main-headding', $main_title ); } else { // 原delete_option键名错误,和update的键名保持一致 delete_option( 'state-reps-main-headding' ); } if ( isset( $_POST['state-reps-text'] ) ) { $state_reps_text = sanitize_text_field( wp_unslash( $_POST['state-reps-text'] ) ); update_option( 'state-reps-text', $state_reps_text ); } else { delete_option( 'state-reps-text' ); } if ( isset( $_POST['current-reps-tab-title'] ) ) { $current_reps_tab_title = sanitize_text_field( wp_unslash( $_POST['current-reps-tab-title'] ) ); update_option( 'current-reps-tab-title', $current_reps_tab_title ); } else { delete_option( 'current-reps-tab-title' ); } if ( isset( $_POST['alumini-tab-title'] ) ) { $alumini_tab_title = sanitize_text_field( wp_unslash( $_POST['alumini-tab-title'] ) ); update_option( 'alumini-tab-title', $alumini_tab_title ); } else { delete_option( 'alumini-tab-title' ); } if ( isset( $_POST['current-reps-text'] ) ) { $current_reps_text = sanitize_text_field( wp_unslash( $_POST['current-reps-text'] ) ); update_option( 'current-reps-text', $current_reps_text ); } else { delete_option( 'current-reps-text' ); } if ( isset( $_POST['no-current-reps-text'] ) ) { $no_current_reps_text = sanitize_textarea_field( wp_unslash( $_POST['no-current-reps-text'] ) ); update_option( 'no-current-reps-text', $no_current_reps_text ); } else { delete_option( 'no-current-reps-text' ); } if ( isset( $_POST['no-state-reps-text'] ) ) { $no_state_reps_text = sanitize_textarea_field( wp_unslash( $_POST['no-state-reps-text'] ) ); update_option( 'no-state-reps-text', $no_state_reps_text ); } else { delete_option( 'no-state-reps-text' ); } // 保存完成后跳转,避免表单重复提交 wp_safe_redirect( add_query_arg( 'settings-updated', 'true', wp_get_referer() ) ); exit; }
3. 修正设置页表单代码
明确指定表单提交地址,修正重复的文本域ID,增加输出转义和保存成功提示:
<div class="wrap"> <h1><?php echo esc_html( $settings_title ); ?></h1> <?php // 输出保存成功提示 if ( isset( $_GET['settings-updated'] ) && 'true' === $_GET['settings-updated'] ) { ?> <div class="notice notice-success is-dismissible"> <p><?php _e( 'Settings saved successfully.', 'alumns-reps-settings' ); ?></p> </div> <?php } ?> <!-- 明确指定表单提交地址 --> <form method="post" action="<?php echo esc_url( admin_url( 'edit.php?post_type=alumns-reps&page=alumns-settings' ) ); ?>"> <table class="form-table" role="presentation"> <tr> <th scope="row"><label for="state-reps-headding"><?php _e( 'Headding' ); ?></label></th> <td><input name="state-reps-headding" type="text" id="state-reps-headding" value="<?php echo esc_attr( get_option('state-reps-main-headding') ); ?>" class="regular-text" /></td> </tr> <tr> <th scope="row"><label for="state-reps-text"><?php _e( 'State Reps Title' ); ?></label></th> <td><input name="state-reps-text" type="text" id="state-reps-text" aria-describedby="tagline-description" value="<?php echo esc_attr( get_option('state-reps-text') ); ?>" class="regular-text" /> </td> </tr> <tr> <th scope="row"><label for="current-reps-tab-title"><?php _e( 'Current Rep Tab Title' ); ?></label></th> <td><input name="current-reps-tab-title" type="text" id="current-reps-tab-title" aria-describedby="tagline-description" value="<?php echo esc_attr( get_option('current-reps-tab-title') ); ?>" class="regular-text" /> </td> </tr> <tr> <th scope="row"><label for="alumini-tab-title"><?php _e( 'Alumini Tab Title' ); ?></label></th> <td><input name="alumini-tab-title" type="text" id="alumini-tab-title" aria-describedby="tagline-description" value="<?php echo esc_attr( get_option('alumini-tab-title') ); ?>" class="regular-text" /> </td> </tr> <tr> <th scope="row"><label for="current-reps-text"><?php _e( 'Current Reps Title' ); ?></label></th> <td><input name="current-reps-text" type="text" id="current-reps-text" aria-describedby="tagline-description" value="<?php echo esc_attr( get_option('current-reps-text') ); ?>" class="regular-text" /> </td> </tr> <tr> <th scope="row"><?php _e( 'No Current Reps' ); ?></th> <td><fieldset><legend class="screen-reader-text"><span><?php _e( 'No Current Reps' ); ?></span></legend> <textarea name="no-current-reps-text" rows="5" cols="50" id="no-current-reps-text" class="large-text code"><?php echo esc_textarea( get_option('no-current-reps-text') ); ?></textarea> </fieldset></td> </tr> <tr> <th scope="row"><?php _e( 'No State Reps' ); ?></th> <td><fieldset><legend class="screen-reader-text"><span><?php _e( 'No State Reps' ); ?></span></legend> <!-- 修正重复ID --> <textarea name="no-state-reps-text" rows="5" cols="50" id="no-state-reps-text" class="large-text code"><?php echo esc_textarea( get_option('no-state-reps-text') ); ?></textarea> </fieldset></td> </tr> </table> <?php submit_button(); ?> <?php wp_nonce_field( 'alumns-reps-settings-page-save', 'alumns-reps-settings-page-save-nonce' ); ?> </form> </div><!-- .wrap -->
注意:所有输出到表单value属性的内容都需要用
esc_attr做转义,避免特殊字符导致表单属性断裂。
内容的提问来源于stack exchange,提问作者Newsam
相关产品推荐
相关产品推荐

