带有选项卡式导航的设置API中的随机数

时间:2019-06-28 作者:Chris J. Zähller

我之所以重写这篇文章,是因为正如有人正确指出的那样,原来的帖子“严重不足”我制作了一个插件,它有各种设置的选项卡视图。基于此Wordpress Plugin Template. 该模板使用WP Settings API构建设置页面并显示选项卡。表单使用默认值_wpnonce 用于“提交/保存设置”按钮。

选项卡是改变页面URL查询字符串的链接元素,例如/wp-admin/options-general.php?page=my_plugin_settings&tab=tab_1/wp-admin/options-general.php?page=my_plugin_settings&tab=tab_2.

现在的问题是:如何为选项卡创建一个nonce,并在页面加载或用户选择其中一个选项卡时进行检查。

class-my-plugin.php. 为了清晰起见,简化了代码示例。

class My_Plugin {

    private static $_instance = null;

    public $admin = null;

    public $settings = null;

    public $_token;

    public function __construct( $file = \'\', $version = \'1.0.0\' ) {
        $this->_version = $version;
        $this->_token   = \'my_plugin\';
    }
}
class-my-plugin-settings.php. 为了清晰起见,简化了代码示例。

class My_Plugin_Settings {

    private static $_instance = null;

    public $parent = null;

    public $base = \'\';

    public $settings = array();

    public function __construct( $parent ) {
        $this->parent = $parent;

        $this->base = \'wpt_\';

        add_action( \'init\', array( $this, \'init_settings\' ), 11 );

        add_action( \'admin_init\', array( $this, \'register_settings\' ) );

        add_action( \'admin_menu\', array( $this, \'add_menu_item\' ) );

        add_filter( $this->base . \'menu_settings\', array( $this, \'configure_settings\' ) );
    }

    /**
     * Initialise settings
     */
    public function init_settings() {
        $this->settings = $this->settings_fields();
    }

    /**
     * Add settings page to admin menu
     */
    public function add_menu_item() {
        // Code omitted for brevity.
    }

    /**
     * Prepare default settings page arguments
     */
    private function menu_settings() {
        // Code omitted for brevity.
    }

    /**
     * Container for settings page arguments
     */
    public function configure_settings( $settings = array() ) {
        return $settings;
    }

    /**
     * Build settings fields
     */
    private function settings_fields() {

        $settings[\'tab_1\'] = array(
            \'title\'       => __( \'Tab 1\', \'my_plugin\' ),
            \'description\' => __( \'The first settings screen.\', \'my_plugin\' ),
            \'fields\'      => array(
                // Form fields etc. here
            ),
        );

        $settings[\'tab_2\'] = array(
            \'title\'       => __( \'Tab 2\', \'my_plugin\' ),
            \'description\' => __( \'The second settings screen.\', \'my_plugin\' ),
            \'fields\'      => array(
                // Form fields etc. here
            ),
        );

        $settings = apply_filters( $this->parent->_token . \'_settings_fields\', $settings );

        return $settings;
    }

    /**
     * Register plugin settings
     */
    public function register_settings() {
        if ( is_array( $this->settings ) ) {

            // Check posted/selected tab.
            $current_section = \'\';
            if ( isset( $_POST[\'tab\'] ) && $_POST[\'tab\'] ) { // NONCE warning
                $current_section = $_POST[\'tab\']; // NONCE warning
            } else {
                if ( isset( $_GET[\'tab\'] ) && $_GET[\'tab\'] ) { // NONCE warning
                    $current_section = $_GET[\'tab\']; // Nonce warning
                }
            }

            foreach ( $this->settings as $section => $data ) {

                if ( $current_section && $current_section !== $section ) {
                    continue;
                }

                // Add section to page.
                add_settings_section( $section, $data[\'title\'], array( $this, \'settings_section\' ), $this->parent->_token . \'_settings\' );

                foreach ( $data[\'fields\'] as $field ) {

                    // Validation callback for field.
                    $validation = \'\';
                    if ( isset( $field[\'callback\'] ) ) {
                        $validation = $field[\'callback\'];
                    }

                    // Register field.
                    $option_name = $this->base . $field[\'id\'];
                    register_setting( $this->parent->_token . \'_settings\', $option_name, $validation );

                    // Add field to page.
                    add_settings_field(
                        $field[\'id\'],
                        $field[\'label\'],
                        array( $this->parent->admin, \'display_field\' ),
                        $this->parent->_token . \'_settings\',
                        $section,
                        array(
                            \'field\'  => $field,
                            \'prefix\' => $this->base,
                        )
                    );
                }

                if ( ! $current_section ) {
                    break;
                }
            }
        }
    }

    /**
     * Settings section.
     *
     * @param array $section Array of section ids.
     * @return void
     */
    public function settings_section( $section ) {
        $html = \'<p> \' . $this->settings[ $section[\'id\'] ][\'description\'] . \'</p>\' . "\\n";
        echo $html;
    }

    /**
     * Load settings page content.
     *
     * @return void
     */
    public function settings_page() {

        // Build page HTML.
        $html      = \'<div class="wrap" id="\' . $this->parent->_token . \'_settings">\' . "\\n";
            $html .= \'<h2>\' . __( \'Plugin Settings\', \'my_plugin\' ) . \'</h2>\' . "\\n";

            $tab = \'\';
        //phpcs:disable
        if ( isset( $_GET[\'tab\'] ) && $_GET[\'tab\'] ) {
            $tab .= $_GET[\'tab\'];
        }
        //phpcs:enable

        // Show page tabs.
        if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {

            $html .= \'<h2 class="nav-tab-wrapper">\' . "\\n";

            $c = 0;
            foreach ( $this->settings as $section => $data ) {

                // Set tab class.
                $class = \'nav-tab\';
                if ( ! isset( $_GET[\'tab\'] ) ) { // NONCE warning
                    if ( 0 === $c ) {
                        $class .= \' nav-tab-active\'; 
                    }
                } else {
                    if ( isset( $_GET[\'tab\'] ) && $section == $_GET[\'tab\'] ) { // Nonce warning
                        $class .= \' nav-tab-active\';
                    }
                }

                // Set tab link.
                $tab_link = add_query_arg( array( \'tab\' => $section ) );
                if ( isset( $_GET[\'settings-updated\'] ) ) { // NONCE warning
                    $tab_link = remove_query_arg( \'settings-updated\', $tab_link );
                }

                // Output tab.
                $html .= \'<a href="\' . $tab_link . \'" class="\' . esc_attr( $class ) . \'">\' . esc_html( $data[\'title\'] ) . \'</a>\' . "\\n";

                ++$c;
            }

            $html .= \'</h2>\' . "\\n";
        }

            $html .= \'<form method="post" action="options.php" enctype="multipart/form-data">\' . "\\n";

                // Get settings fields.
                ob_start();
                settings_fields( $this->parent->_token . \'_settings\' );
                do_settings_sections( $this->parent->_token . \'_settings\' );
                $html .= ob_get_clean();

                $html .= \'<p class="submit">\' . "\\n";
                $html .= \'<input type="hidden" name="tab" value="\' . esc_attr( $tab ) . \'" />\' . "\\n";
                $html .= \'<input name="Submit" type="submit" class="button-primary" value="\' . esc_attr( __( \'Save Settings\', \'my_plugin\' ) ) . \'" />\' . "\\n";
                $html .= \'</p>\' . "\\n";
                $html .= \'</form>\' . "\\n";
                $html .= \'</div>\' . "\\n";

        echo $html;
    }

    /**
     * Main My_Plugin_Settings Instance
     *
     * Ensures only one instance of My_Plugin_Settings is loaded or can be loaded.
     *
     * @since 1.0.0
     * @static
     * @see My_Plugin()
     * @param object $parent Object instance.
     * @return object My_Plugin_Settings instance
     */
    public static function instance( $parent ) {
        if ( is_null( self::$_instance ) ) {
            self::$_instance = new self( $parent );
        }
        return self::$_instance;
    } // End instance()

}
Form output. 根据选择的选项卡进行更改。

<div class="wrap">
    <h2>Heading</h2>
    <p>Plugin description.</p>
    <h2 class="nav-tab-wrapper">
        <a href="/wp-admin/options-general.php?page=my_plugin_settings&amp;tab=tab_1" class="nav-tab nav-tab-active">Tab 1</a>
        <a href="/wp-admin/options-general.php?page=my_plugin_settings&amp;tab=tab_2" class="nav-tab">Tab 2</a>
    </h2>
    <form method="post" action="options.php" enctype="multipart/form-data">
        <input type="hidden" name="option_page" value="my_plugin_settings"><input type="hidden" name="action" value="update"><input type="hidden" id="_wpnonce" name="_wpnonce" value="$integer"><input type="hidden" name="_wp_http_referer" value="/wp-admin/options-general.php?page=my_plugin_settings&amp;tab=tab_1">
        <h2>Tab 1</h2>
        <p>
            Description for Tab 1 screen.</p>
        <table class="form-table">
            <!-- Form table contents -->
        </table>
        <p class="submit">
            <input type="hidden" name="tab" value="upload">
            <input name="Submit" type="submit" class="button-primary" value="Save Settings">
        </p>
    </form>
</div>
原始帖子:我正在使用PHPC和Wordpress-Extra编码标准来检查我的插件代码。我收到以下警告:

警告|在未进行nonce验证的情况下处理表单数据。

相关代码在设置页面中显示选项卡式导航:

class Example_Class {
    public function settings_page() {
        $tab = \'\';

        if ( isset( $_GET[\'tab\'] ) && $_GET[\'tab\'] ) { // WARNING
            $tab .= $_GET[\'tab\']; // WARNING
        }

        if ( isset( $_GET[\'tab\'] ) && $_GET[\'tab\'] ) { // WARNING
            $tab .= $_GET[\'tab\']; // WARNING
        }

        // Show page tabs
        if ( is_array( $this->settings ) && 1 < count( $this->settings ) ) {

            $html .= \'<h2 class="nav-tab-wrapper">\' . chr( 0x0D ) . chr( 0x0A );

            $c = 0;
            foreach ( $this->settings as $section => $data ) {

                // Set tab class
                $class = \'nav-tab\';
                if ( ! isset( $_GET[\'tab\'] ) ) { // WARNING
                    if ( 0 === $c ) {
                        $class .= \' nav-tab-active\';
                    }
                } else {
                    if ( isset( $_GET[\'tab\'] ) && $section === $_GET[\'tab\'] ) { // WARNING
                        $class .= \' nav-tab-active\';
                    }
                }

                // Set tab link
                $tab_link = add_query_arg( array( \'tab\' => $section ) );
                if ( isset( $_GET[\'settings-updated\'] ) ) { // WARNING
                    $tab_link = remove_query_arg( \'settings-updated\', $tab_link );
                }

                // Output tab
                $html .= \'<a href="\' . $tab_link . \'" class="\' . esc_attr( $class ) . \'">\' . esc_html( $data[\'title\'] ) . \'</a>\' . chr( 0x0D ) . chr( 0x0A );

                ++$c;
            }

            $html .= \'</h2>\' . chr( 0x0D ) . chr( 0x0A );
        }
    }

    public function register_settings() {
        if ( is_array( $this->settings ) ) {

            // Check posted/selected tab
            $current_section = \'\';
            if ( isset( $_POST[\'tab\'] ) && $_POST[\'tab\'] ) { // WARNING
                $current_section = $_POST[\'tab\'];
            } else {
                if ( isset( $_GET[\'tab\'] ) && $_GET[\'tab\'] ) { // WARNING
                    $current_section = $_GET[\'tab\']; // WARNING
                }
            }

            // Unrelated code omitted
        }
    }
}
我以为API自动处理nonce?我应该担心吗?或者代码是否正常?如果没有,我应该如何解决这个问题?

编辑:根据答案,API提供默认nonce<input type="hidden" id="_wpnonce" name="_wpnonce" value="$int"> &;关联的隐藏字段<input type="hidden" name="_wp_http_referer" value="/wp-admin/options-general.php?page=_settings">. 如何验证?我尝试过,但没有成功:

if ( isset( $_POST[\'tab\'] ) && $_POST[\'tab\'] ) {
    if ( ! wp_verify_nonce( \'_wpnonce\' ) ) {
        wp_die( \'Go away!\' );
    } else {
        $current_section = sanitize_text_field( wp_unslash( $_POST[\'tab\'] ) );
    }
} else {
    if ( isset( $_GET[\'tab\'] ) && $_GET[\'tab\'] ) {
        if ( ! wp_verify_nonce( \'_wpnonce\' ) ) {
            wp_die( \'Go away!\' );
        } else {
            $current_section = sanitize_text_field( wp_unslash( $_GET[\'tab\'] ) );
        }
    }
}

2 个回复
最合适的回答,由SO网友:Otto 整理而成

API处理表单部件的nonce,因为您正在使用settings_fields call,它输出*-选项nonce,您将数据传递给options.php 用于保存的文件,它在保存设置之前为您检查该nonce。设置API确实为您完成了这一部分。

但是,您的选项卡代码不是这种形式。这只是链接。链接上没有nonce,您必须使用GET部分中的数据的代码不进行任何nonce检查。

现在,从技术上讲,只要不在此处保存任何数据,这就可以了。nonce的目的是在提交数据时验证意图,如果您没有提交任何以任何实际方式保存或使用的数据,则无需验证该意图。在这里,您的选项卡选择所做的唯一一件事就是更改页面上显示的字段。

您可能需要考虑完全删除选项卡,并在同一页面上显示整个表单以及所有设置。如果您希望以选项卡形式组织,最好使用javascript或CSS来装饰页面。还要考虑可访问性,因为对于希望一次进行所有更改的用户来说,将表单作为一个整体可能更好,而不必在多个页面上配置内容或在页面顶部使用链接导航到它们。

SO网友:KAGG Design

这是适当的警告。没有API关心nonce。

在读取$\\u GET或$\\u POST之前,必须使用verify\\u nonce()或check\\u admin\\u referer()。

最好使用全套编码标准,简称WordPress,其中包括Core、Docs和Extra。

相关推荐

Handling expired nonces

我有一个有几个按钮的页面。当人们单击按钮时,会发出AJAX请求并更新页面的各个部分。为了安全起见,AJAX请求使用nonce。我注意到,如果有人打开页面的时间超过了nonce过期所需的时间,那么AJAX请求将按预期停止工作。然而,我想要一个更健壮的行为。我想做的是-检测nonce何时过期/失败,然后在该点以某种方式生成一个新的nonce。这是一个由两部分组成的问题:如何检测nonce已过期</在过期的时候,最好的做法是整页刷新还是其他什么?我对安全考虑不太在行。在页面刷新之后,我想继续保持当前页面状