input type=”checkbox” の二重ブラケットについて

WordPress の自作プラグインで挙動不審なところが見付かり、そういえば……と思って input type=”checkbox” の二重ブラケットについて調べてみました。

調査した結果としては、自作プラグインのコードの書き方が悪かったので修正しました。

経緯・現象

WordPress の自作プラグインでカスタムタクソノミーをチェックボックスで選択できる設定画面を作ってあったのですが、そこにチェックを入れてもデータが保存されない現象に遭遇。

コード

<?php

require_once( ABSPATH . '/wp-admin/includes/template.php' );

class MyCategoryChecklistWalker extends \Walker_Category_Checklist
{
    /**
     * var
     */
    protected $c;
    protected $hidden_ids;
    /**
     * コンストラクタ
     */
    function __construct( $c, $arrayTaxonomies )
    {
        $this->c          = $c;
        $this->hidden_ids = $arrayTaxonomies['id'];
    }

    /**
     * Start the element output.
     *
     * @see Walker::start_el()
     *
     * @since 2.5.1
     *
     * @param string $output   Used to append additional content (passed by reference).
     * @param object $category The current term object.
     * @param int    $depth    Depth of the term in reference to parents. Default 0.
     * @param array  $args     An array of arguments. @see wp_terms_checklist()
     * @param int    $id       ID of the current term.
     */
    function start_el(
        &$output,
        $category,
        $depth = 0,
        $args = Array(),
        $id = 0
    )
    {
        extract( $args );
        if( empty( $taxonomy )) {
            $taxonomy = 'category';
        }

        if( $taxonomy == 'category' ) {
            $name = 'post_category';
        }
        else {
            $name = 'tax_input[' . $taxonomy . ']';
        }

        $class = in_array( $category->term_id, $popular_cats ) ? ' class="popular-category"' : '';

        // 処理
        $id = esc_attr( $name ) . '___term---' . esc_attr( $category->term_id );
        $nameAttr = "name=\"{$i( 'myplugin_checkboxes' )}[{$id}]\"";
        // 処理
    }
}

設定画面は WordPress の Walker_Category_Checklist を継承したクラスで、チェックボックスの設定画面を作成していました。

特に今回気になったのは次の部分。

    else {
        $name = 'tax_input[' . $taxonomy . ']';
    }

もう一つ。

    $id = esc_attr( $name ) . '___term---' . esc_attr( $category->term_id );
    $nameAttr = "name=\"{$i( 'myplugin_checkboxes' )}[{$id}]\"";

デフォルトのカテゴリーならば post_category という文字列を付与していますが、カスタムタクソノミーの場合は tax_input[TAXONOMY_NAME] とブラケットを使ってオブジェクトの記述にしていました。これをチェックボックスの name 属性に使用しています。その際に、複数選択可能なチェックボックスなのでさらに外側にブラケットが付くようにしていました。

name="myplugin_checkboxes[tax_input[TAXONOMY_NAME]___term---TERMID]"

こんな感じですね。ところが、このチェックボックスにチェックを入れても反映されない、となったわけです。

いかにも二重にブラケットで括っているのが挙動不審の原因になっていそうな気がしたので、調査しました。

調査

a:XX:{s:26:"post_category___term---154";i:1;s:26:"post_category___term---165";i:1;s:26: /* 略 */ i:1;s:32:"tax_input[TAXONOMY_NAME";i:1;}

データベースで該当するキーを確認すると……やはり、二重ブラケットになった値を入れるところでシリアライズされたデータのフォーマットが崩れています。

検証

そこで簡単なコードを作成。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>input</title>
</head>
<body>
    <form action="./test.php" method="post">
        <label for="input">
            <input type="checkbox" name="input[hoge[fuga]]" id="input" value="1">
            インプット
        </label>
        <button type="submit">送信</button>
    </form>
</body>
</html>

二重ブラケットのチェックボックスを用意して POST するだけの簡単なフォームです。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>result</title>
</head>
<body>
    <pre><code>
    <?php var_dump($_SERVER); ?>
    </code></pre>
    <pre><code>
    <?php var_dump($_REQUEST); ?>
    </code></pre>
</body>
</html>

テストなのでエスケープとか一切していないですが、これで出力を確認。

array (size=1)
  'input' => 
    array (size=1)
      'hoge[fuga' => string '1' (length=1)

思った通り、二重ブラケットのキーが途中で壊れてしまっています。 DB の中でシリアライズが崩れたデータと同じ状態です。

これで二重ブラケットがNGであることが分かったのでコードを修正します。

修正

    else {
//        $name = 'tax_input[' . $taxonomy . ']';
        $name = $taxonomy;
    }

今回は独自のパース処理でタームのチェックを判断しているので、 tax_input の配列名は不要でした。そこで、タクソノミー名をそのまま $name に渡すように修正を加えました。

    $id = esc_attr( $name ) . '___term---' . esc_attr( $category->term_id );
    $nameAttr = "name=\"{$i( 'myplugin_checkboxes' )}[{$id}]\"";

それを使ってチェックボックスを出力する部分や他の部分は一切触らず。これで動作OKを確認しました。

参考

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。