web-dev-qa-db-ja.com

タブ付きナビゲーションの設定APIのナンス

正しく指摘されているように、元の投稿は「ひどく不十分」だったため、これを言い換えています。さまざまな設定のタブ付きのビューを持つプラグインを作成しました。これは Wordpressプラグインテンプレート に基づいています。テンプレートはWP設定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)。

問題は次のとおりです。タブのナンスを作成し、ページが読み込まれたとき、またはユーザーがいずれかのタブを選択したときにそれをチェックする方法です。

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()

}

フォーム出力。選択されているタブによって変わります。

<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>

元の投稿:WordCSでPHPCSを使用しています-追加 プラグインコードをチェックするためのコーディング標準。私はこの警告を受けています:

警告| 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がノンスを自動的に処理すると思いましたか?心配すべきですか?または、コードはそのままでも大丈夫ですか?そうでない場合、どうすれば修正できますか?

編集:回答に照らして、APIはデフォルトのナンス<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'] ) );
        }
    }
}
3

* -options nonceを出力するsettings_fields呼び出しを使用しており、データをoptions.phpファイルに渡して保存するため、APIがフォームパーツのnonceを処理します。設定を保存する前に、あなたのためにそのナンス。この部分は、Settings APIが実際に行います。

ただし、タブコードはその形式ではありません。それは単なるリンクです。リンクにはノンスがありません。GETパーツからのデータを使用する必要があるコードは、ノンスチェックを行いません。

ここで、データをここに保存しない限り、技術的にはこれで問題ありません。 nonceの目的は、データを送信するときに意図を確認することです。実際の方法で保存または使用されるデータを送信しない場合は、その意図を確認する必要はありません。ここでタブを選択する唯一のことは、ページに表示されるフィールドを変更することです。

タブを完全になくし、フォーム全体をすべての設定で同じページに表示することを検討してください。タブ形式の編成が必要な場合は、JavaScriptまたはCSSを使用してページを装飾することをお勧めします。複数のページで構成したり、ページの上部にあるリンクを使用してそれらに移動したりするよりも、一度にすべての変更を行いたいユーザーにとって、フォームをそのままにする方がよい場合があるため、アクセシビリティについても検討してください。

1
Otto

これは適切な警告です。 nonceを処理するAPIはありません。

$ _GETまたは$ _POSTから読み取る前に、verify_nonce()またはcheck_admin_referer()を使用する必要があります。

そして、単にWordPressという名前のコーディング標準の完全なセットを使用することをお勧めします。これには、Core、Docs、Extraが含まれます。

1
KAGG Design