ナビメニューに編集項目を追加する方法

ちょっとだけ厄介なカスタマイズについてです。

ナビメニューの設定項目を自由に編集したいと思った方もいるでしょう。

Nora
Nora

以前にも紹介したことがあった気がしますが、雑だった気がしますので、改めて紹介しておこうと思います。

ただ、探せどフロントエンド側で詳細を追加するなどの処理を加えてサポートすることは可能ですが、管理画面での編集時のフォームを編集するのはちょっと面倒です。

テーマ「Ace」でメニューにアイコンのセットしたり、子メニューの表示を切り替えれられるようにしようと久々に編集しているので、ちょっと関連付けてその方法の一例を紹介したいと思います。

  • Table of Contents

Walkerクラス内にフックが存在しない

フロントエンド側ではフィルターフック「walker_nav_menu_start_el」などを使用することで、生成されたナビメニューに編集を加えることが可能です。

テーマ編集する場合に「詳細」を追加する時などに使用されることが多いと思います。

では、管理画面側では何を使用すれば良いのかですが、ちょっとクラス「Walker_Nav_Menu_Edit(ソースコード参照ページ)」内を見てみましょう。

各メニューアイテムのフォームを生成しているメソッドが「start_el」に当たるわけですが、「do_action」や「apply_filters」といった関数が見つかりません。

つまり、「add_action」や「add_filter」によるフォームの追加ができないことになります。

まぁコアファイルなので、アップデートによって追加されるかもしれませんが、少なくともバージョン「5.1」の段階ではまだ追加されていないように見えます。

では、どうするかという解決策の話を次の項目からしていきます。

Walkerの継承クラスを適用させる

ナビメニューのフォームは前の項目で簡単に解説した通り、クラス「Walker_Nav_Menu_Edit」のメソッド「start_el」が使用されています。

これを編集するために、行う処理は以下の通りです。

  • クラス「Walker_Nav_Menu_Edit」を継承したWalkerクラスを作成
  • 適用されるWalkerクラス名を入れ替え
  • アップデート用のメソッドを追加してフック
  • フロントエンドでデータを取得

これらの基本処理を抑えておけば、ナビメニューアイテムは自由に編集出来るといって過言ではないでしょう。

因みに、ナビメニューの各アイテムは投稿タイプ「ナビメニューアイテム(仮)」の1ページというデータの扱いがなされますので、各アイテムには投稿IDが付与されており、「get_post_meta」や「update_post_meta」でデータの取得や保存などが可能です。

では、実際の処理についてです。

継承クラスの作成

新規ファイルを開いて、以下のコードを入力します。

<?php

if ( ! class_exists( 'Walker_Nav_Menu_Edit' ) ) require_once( ABSPATH . 'wp-admin/includes/class-walker-nav-menu-edit.php' );

class Custom_Walker_Nav_Menu_Edit extends Walker_Nav_Menu_Edit {

  start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
    $output .= parent::start_el( &$output, $item, $depth, $args, $id );
  }

}

これで親のクラスを継承したクラスが生成されますので、管理画面でインクルードされるようにして定義しておきましょう。

Walkerクラスのメソッド「start_el」は第一引数が参照となっていますので、このメソッドにより生成されるHTMLを編集する際は以下のように手を加えます。

start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
  $parent = '';
  parent::start_el( $parent, $item, $depth, $args, $id );
  ob_start();
  ?>
    <p>Inserted Paragraph</p>
  <?php
  $custom_fields = ob_get_clean();
  $output .= preg_replace(
    '/([^\>]+\<\/p>)([^\<]+)?(\<fieldset)/ims',
    '${1}' . $custom_fields . '${3}',
    $parent
  );
}

「$parent」という変数に親メソッドで生成されるHTMLをいったん取得させ、それを加工した物を「$output」に追加しています。

具体的にどう加工しているかというと、HTMLタグ「fieldset」の前(WP5.1では標準のフォームと「移動ひとつ下へ」などの間)にHTMLを挿入する処理です。

これはターゲットとなるコアのWalkerクラス内のHTMLが編集されてしまうと編集し直さないといけないのですが、フックが存在しないので仕方ありません。

例えば、上のコード例では正規表現内でクラスなどの指定を一切していないのですが、これは現在のバージョンでは標準的に出力されるHTML内にfieldsetというタグが1つしか出力されないために出来ることですので、もっと厳選した方が良いかもしれません。

また、コード例ではパラグラフでテキスト「Inserted Paragraph」を挿入していますが、実際にはテンプレートファイルをインクルードしたり、関数「do_action」をコールするなど任意の方法で大丈夫ですが、ここではちゃんと任意のHTMLなどが挿入できることを確認してください。

追加したいフォームの「name」属性などについては後の項目の更新方法で紹介致します。

※ 昔勉強しながら紹介した記事では、テンプレートをコピペして使っていたんですが、いつ更新されるか分かりませんので、正規表現を使って入れ替える方法で大丈夫だろうと、上の方法を紹介しています。

※ 更新に伴いワードプレスコアファイルにフックが追加されれば、そちらを使用することをお勧めします。

適用されるWalkerクラスの入れ替え

この処理は非常に簡単で、フィルターフック「wp_edit_nav_menu_walker」を使用します。

// Walker Class Name
add_filter( 'wp_edit_nav_menu_walker', 'replace_walker_nav_menu_edit', 10, 2 )
function replace_walker_nav_menu_edit( $walker_nav_menu_edit, $menu_id ) {
  if( ! class_exists( 'CustomWalkerNavMenuEdit' ) ) include_once( CUSTOM_WALKER_CLASS_PATH );
  return 'Custom_Walker_Nav_Menu_Edit';
}

定数「CUSTOM_WALKER_CLASS_PATH」には、作成した継承クラスのファイルのパスを使用してください。

これで管理画面でナビメニューを開くと、使用されるWalkerクラスが入れ替わり、メニューアイテムを開くと「Inserted Paragraph」が出力されていると思います。

アップデート用のメソッドを追加

基本的な保存方法はアクションフック「wp_update_nav_menu_item」でIDなどのデータを受け取って処理します。

add_action( 'wp_update_nav_menu_item', 'update_custom_nav_menu_item' ), 10, 3 );
function update_custom_nav_menu_item( $menu_id, $menu_item_db_id, $args ) {

  # Before Save
  if( ! current_user_can( 'manage_options' ) ) {
    return;
  }

  # Sanitize Type
  $test_data = sanitize_text_field( 
    isset( $_POST['test-data'][ $menu_item_db_id ] ) 
    ? $_POST['test-data'][ $menu_item_db_id ]
    : ''
  );

  # Save Meta
  update_post_meta( $menu_item_db_id, '_test_data', $test_data );

}

ポストで受け取るデータにはそれぞれIDで各ナビメニューアイテムを区別していますので、フォームを生成する際には属性値「name」で付与し忘れないでください。

これを忘れると、どのナビメニューアイテムに対してセットしたデータなのかを特定できません。

サニタイズは各自データのタイプなどに応じて自作しましょう。

では、フォームの作成ですが、フォームの基本は以下の通りです。

<?php
$item_id = intval( $item->ID );
$test_data = get_post_meta( $item_id, '_test_data', true );
?>

<input type="text" 
    id="test-data-<?php echo esc_attr( $item_id ); ?>" 
    class="test-data" 
    name="test-data[<?php echo esc_attr( $item_id ); ?>]" 
    value="<?php echo esc_attr( $test_data ); ?>"
/>

名前の後にIDが必要になりますので、これを忘れないようにしましょう。

フロントエンドで利用

これも非常に簡単です。

詳細などを追加する際に利用されるフィルターフック「walker_nav_menu_start_el」を使用します。

add_filter( 'walker_nav_menu_start_el', 'custom_nav_menu_start_el', 10, 4 );
function ( $item_output, $item, $depth, $args ) {

  $item_id = intval( $item->ID );
  $test_data = get_post_meta( $item_id, '_test_data', true );

  $item_output = str_replace(
    $args->link_after,
    $test_data . $args->link_after,
    $item_output
  );

}

受け取る変数のリストは以下の通りですので、ご参考に

  1. 出力されるナビメニューアイテム1個分のHTML
  2. ナビメニューアイテムの投稿データ
  3. ナビメニューアイテムの出力される階層
  4. wp_nav_menuで設定されているデータリスト

といった形になっていますので、第二引数の「$item」よりIDを取得し、ポストメタとして保存したデータをここでは自由に取得できます。

結構無理矢理に「リンク後」の手前に挿入する形にしていますが、テストですので、これで保存したデータが出力されていればOKです。

あとは応用して自由に編集してください。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください