You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 01:57:22