Page No.1

またしてもいまさらの、という感じで wp_nav_menu のいくつかのフックに関して。そして久々の WordPress ネタとなる。
まぁ、自分のサイトということであれば、php というか html で直接書いてしまったほうが、よほど手間もかからないし、サイトの表示の速さという点でもアドバンテージがあると思われる。
しかし、それが他の人たち用の WP となると全く話が違ってくる。html の知識のない人たちにとっては、編集するのにとっつきやすい、この wp_nav_menublock editor はとても有用なものなのだろう。

と、いうことで自分としては久々の wp_nav_menu と付き合うこととなる。
まぁ、ひさしぶりに触ってみたけれど、これはそんなに変わっていないのかな!という印象。そしてこれも相変わらずなんだけれど、鬱陶しいほどの class が付いてくる。ほとんど使い道のないものばかりのように思えるのだけれど、果たしてこれだけの class をつけることに意味があるのだろうか!

まずは、その辺りの整理ができないものかということで、フックを探してソースコードを漁ってみる。
wp_nav_menu のある wp-includes/nav-menu-template.php にもいくつかフックがあるけれど、もっと直接的にいじれるものはないのだろうかと、wp-includes/class-walker-nav-menu.php の方も探してみると、こちらの方に目的に沿うであろうフックを見つけることができた。

まずは、これ、nav_menu_css_class というフィルターフック。
このフックは、「Starts the element output.」の説明のついている、function start_el () の中にある。

<?php
    // wp-includes/class-walker-nav-menu.php
    // function start_el()

	/**
	 * Filters the CSS classes applied to a menu item's list item element.
	 *
	 * @since 3.0.0
	 * @since 4.1.0 The `$depth` parameter was added.
	 *
	 * @param string[] $classes   Array of the CSS classes that are applied to the menu item's `<li>` element.
	 * @param WP_Post  $menu_item The current menu item object.
	 * @param stdClass $args      An object of wp_nav_menu() arguments.
	 * @param int      $depth     Depth of menu item. Used for padding.
	 */
	$class_names = implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $menu_item, $args, $depth ) );
	$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
?>
PHP
CopyExpand

得られる引数の $classes は、説明にあるように、適用されるべき class の文字列が入っている配列とのこと。なるほどっと、その配列をいじればいかようにでも出来るってことのよう。
その他の引数は、その menu item の様々な情報が入っているので、それによって分別して処理が行える。ここで言う menu item というのは、個々のリンクのことである。anchor タグにされて、class を設定され、li タグにてラップされる、その一つ一つのリンクの要素のこと。引数 $menu_item で得られる内容を見てみると、その項目が投稿で得られる情報と同じようなので、データベースを覗いてみると、wp_posts テーブルに投稿と同じように保存されていた。この場合の post_type は “nav_menu_item” である。

menu_item である個々のリンク要素を、投稿と同じようにデータベースに保存しているのにちょっと驚いたが、はて、それらをまとめて一つの navi menu にするのはどうするのだろうか、とテーブルを漁ってみると、term を使ってそれぞれ個々の menu_item と紐付けているのがわかった。navi menu を新規作成するときにつけたメニュー名は、wp_terms テーブルの name に見つけることが出来る。

と、いうことで、この $menu_item という引数から得られるのは、個々のリンク要素としての情報で、例えばそのリンクのラベルの文字列 ( post_title ) だとか、本来のリンクの url とか slug ( post_name ) とか、保存した月日とかである。

一方、それに対して $args の方はといえば、こちらは taxonomy としての情報となり、 navi menu としてのメニュー名だとか、menu を設定するときに必要な theme_location とか、wp_nav_menu() に設定した引数の値などを得ることが出来る。$args で得られる メニュー名によって、いま処理されている menu item がどの navi menu に属しているリンクかを判別出来るということである。

そして、$depth は入れ子になったリストの階層のレベルを知ることが出来る。一段目が 0。一つ下の階層が 1、2つ下が 2、という具合に数値で返してくれる。

それでは、実際のコード。
例えばそのサイトは日本語、英語、中国語の三ヶ国表示が必要で、メニューもそれぞれ用にと三種類を用意しているとして、Home へのリンクは特別にそれ用の class を付け、階層の深いリストは一段目と区別するための class を付ける。それ以外は不必要なので class は必要なし、ということならこんな具合。

<?php
    function nav_menu_del_class( $classes , $menu_item, $args, $depth ) {
        $is_home = array(  'ホーム' => '', 'home' => '', '主页' => '' );

        if ( isset ( $is_home[ $menu_item->title ] ) ) {
      	      // クラスを2つ設定
        	$classes = array( 'menu-item', 'menu-item-home' );
        } elseif ( $depth ) {
        	$classes = array( 'menu-item-sob' );
        } else {
      	      // 配列を空にすればクラスは全く無くなる
            $classes = array();
        }

        return $classes;
    }
    add_filter( 'nav_menu_css_class', 'nav_menu_del_class', 10, 4 );
?>
PHP
CopyExpand

このフックも、nav_menu_css_class と同じ関数内にあり、すぐ後に続くように書いてある。
こちらは、リンクの機能となるアンカータグの、hreftitletargetrelaria-current、といった属性を設定できる。アンカータグ専用。

<?php
    // wp-includes/class-walker-nav-menu.php
    // function start_el()

    /**
     * Filters the HTML attributes applied to a menu item's anchor element.
     *
     * @since 3.6.0
     * @since 4.1.0 The `$depth` parameter was added.
     *
     * @param array $atts {
     *     The HTML attributes applied to the menu item's `<a>` element, empty strings are ignored.
     *
     *     @type string $title        Title attribute.
     *     @type string $target       Target attribute.
     *     @type string $rel          The rel attribute.
     *     @type string $href         The href attribute.
     *     @type string $aria-current The aria-current attribute.
     * }
     * @param WP_Post  $menu_item The current menu item object.
     * @param stdClass $args      An object of wp_nav_menu() arguments.
     * @param int      $depth     Depth of menu item. Used for padding.
     */
    $atts = apply_filters( 'nav_menu_link_attributes', $atts, $menu_item, $args, $depth );

    $attributes = '';
    foreach ( $atts as $attr => $value ) {
        if ( is_scalar( $value ) && '' !== $value && false !== $value ) {
            $value       = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
            $attributes .= ' ' . $attr . '="' . $value . '"';
        }
    }
?>
PHP
CopyExpand

引数の $atts でそれらの属性値を渡してくれるのであるけれど、これはそれらの属性名を key とした連想配列になっている。で、その後の foreach によって、属性名であるキーと値を文字列として連結させて html 文を作っているだけのことなので、”id” なども設定できるのではと思いつつ試してみると、もちろん、それも可能だった。”id” だけでなくたとえば “data-****”とか、”style” も同じことなので、特にスタイルを設定したい特別なリンクがあれば、specificity score が1000 point の強力なインラインスタイルで設定できる。
その他の引数は、nav_menu_css_class フックと全く同じ。

サイトの navi menu において、あるページにおいての html アンカーというのかページアンカーというのは必ずと言ってよいほど使っていると思う。その場合、ページ内の対象の箇所の要素の id属性に#を付けてリンクに付加すればいいだけのことなんだけれど、wp_nav_menu の標準的機能においてはどうにもできないようである。その場合、このフックを使えば出来るということになる。であるからして、このフックは必須であり、とても重要なフックなのである。

と、いうことで、以下となる。
やっていることは、その htmlアンカーと言語指定用のクエリーを付加すること。スタイルを設定しやすいので、”id” 属性をそれぞれのアンカータグに設定し、アンカータグには title 属性があったほうが良いと思うので、それもくっつける、と。

<?php
    function nav_menu_add_query( $atts, $menu_item, $args, $depth ) {
        global $lang;// $lang は自前の変数で言語設定

        $addids = array( '会社理念' =>'#rinen', '沿革' =>'#enkaku', 'vision and Mission' =>'#rinen', 'Our History' =>'#enkaku', '远景和目标' =>'#rinen', '我们的历史' =>'#enkaku' );

        if ( isset( $addids[ $menu_item->title ] ) ) {
            $atts['href'] .= '?lang=' . $lang . $addids[ $menu_item->title ];
        } else {
            $atts['href'] .= '?lang=' . $lang;
        }

        // footer の menu の anchor タグには id は必要ないので無視し、
        // 深い階層の タグにも id は必要ないので empty( $depth ) で除外する
        if ( false === strpos( $args->menu->name, 'footer' ) and empty( $depth ) ) {

            $atts['id'] = 'menu-anc-' . ( string ) $menu_item->ID;
        }

        $atts['title'] = 'link to ' . $menu_item->title . ' page';

        return $atts;
    }
    add_filter( 'nav_menu_link_attributes', 'nav_menu_add_query', 10, 4 );
?>
PHP
CopyExpand

続いては、nav_menu_item_id フック。
これも同じ関数内で、nav_menu_css_classnav_menu_link_attributes に挟まれたところにある。
リンクのアンカータグをラップする li タグにつく “id” 用のフィルター。
引数は “id” のデフォルトの文字列と、これも先の2つのフックと全く同じ三つで、もうすっかりおなじみとなった $menu_item$args$depth

<?php
    // wp-includes/class-walker-nav-menu.php
    // function start_el()

    /**
     * Filters the ID applied to a menu item's list item element.
     *
     * @since 3.0.1
     * @since 4.1.0 The `$depth` parameter was added.
     *
     * @param string   $menu_id   The ID that is applied to the menu item's `<li>` element.
     * @param WP_Post  $menu_item The current menu item.
     * @param stdClass $args      An object of wp_nav_menu() arguments.
     * @param int      $depth     Depth of menu item. Used for padding.
     */
    $id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $menu_item->ID, $menu_item, $args, $depth );
    $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';

    $output .= $indent . '<li' . $id . $class_names . '>';
?>
PHP
CopyExpand

まぁ、これに関しては、”id” はデフォルトですでに設定されているし、何かしら特別な理由がない限り変更する必要もなさそうなので、あまり使うことはないのかもしれない。スタイル設定をどこかで使っていたものを使いまわしていたりして、それに “id” を合わせたいとかそういった事だろうか。と、いいつつ、自分の場合はもろにそのパターンなので、このフックにもしっかりお世話になっている。

<?php
    function nav_menu_set_id ( $item_id, $menu_item, $args, $depth ) {
    
        // footer の navi は変更不要、一段目の階層にあるものだけ変更する
        if ( false === strpos( $args->menu->name, 'footer' ) and empty( $depth ) ) {
    
            $item_id = 'hd-nav-' . ( string ) $menu_item->ID;
        }
        return $item_id;
    }
    add_filter( 'nav_menu_item_id', 'nav_menu_set_id', 10, 4 );
?>
PHP
CopyExpand

このフックは、今までのものとは違う関数内にある。「Starts the list before the elements are added.」という説明書きの付いた function start_lvl() である。これは各リストの ul タグを生成する関数であり、このフックはその過程において ul タグの class を設定できる。デフォルトの class は”sub-menu” であり、それを変更したければこれを使うべし、ってところ。

この関数を見る限りにおいては、設定できるのは class 属性だけであり、id などは付加できないよう。引数の方は、デフォルトの “sub-menu” が一つだけ入っている $classed という配列と、おなじみの、$args$depth である。ul タグは直接的にリンクアイテムを内包しないし、menu item とは一対一対応ではないので、$menu_item はあてがいようがない。

<?php
    // wp-includes/class-walker-nav-menu.php
    // function start_lvl()

    // Default class.
    $classes = array( 'sub-menu' );

    /**
     * Filters the CSS class(es) applied to a menu list element.
     *
     * @since 4.8.0
     *
     * @param string[] $classes Array of the CSS classes that are applied to the menu `<ul>` element.
     * @param stdClass $args    An object of `wp_nav_menu()` arguments.
     * @param int      $depth   Depth of menu item. Used for padding.
     */
    $class_names = implode( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );
    $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';

    $output .= "{$n}{$indent}<ul$class_names>{$n}";
?>
PHP
CopyExpand

実にシンプルであり、変更、または追加したい class の文字列を、$classes の配列に入れて返すだけのことである。
今製作中のサイトでは、サイトマップも wp_nav_menu() で生成するようにしたが、サイトマップの場合は階層が深くなっているので、その階層によって識別できるように class を設定している。

<?php
    function nav_menu_submenu_class( $classes, $args, $depth ) {

        if ( false !== strpos( $args->menu->name, 'site-map' ) ) {
            $childs = array( 'fst', 'scd', 'thd', 'fou' );
            $classes = array( 'childul', 'chil-' . $childs[ $depth ]  );
        } else {
            $classes = array( 'subnav' );
        }

        return $classes;
    }
    add_filter( 'nav_menu_submenu_css_class', 'nav_menu_submenu_class', 10, 3 );
?>
PHP
CopyExpand

その他にも、リンクのタイトルを変更できるフックなどもある。
なんだかんだと言いつつ、色々やっていればそれなりに思っているものに近いものになっている。この自由度の高さが WordPress の一番好きなところだったのだけれど、それが Gutenberg といえば・・・?

と、いうところで、これだけいくつかのフックを駆使しつつ、やっと wp_nav_menu() を使いこなすことができるのだけれども、その上、header footer と両方に navi menu を生成して、ということになると、やはりどれだけ遅くなってしまうのか!と、いうことが気になってしまう。

ローカル環境でやってみても、そんなに遅くなっているという感じはしないのではあるけれど・・・。それでも、やはり直に html ( php ) を書いてあるものよりは負荷が大きいだろうなぁと思う。そして、html の知識のない人たちには、この機能は必須なのだろうし。

まぁ、ナビメニューなんてものは一度作ってしまえば、そんなに頻繁に変更するものでもないのだし、ならばキャッシュ化してしまえばいいじゃないか、と、いうことで、2ページ目では、その navi menu のキャシュ化について、となります。

Leave a Reply!

JavaScript is necessary to send a comment.
You can edit and delete your comment if you input a edit key.
Edit key is necessary for attesting you when you edit and delete it.
The tag of HTML cannot be used in comment.
When you comment for the first time, it is displayed after the approval of the administrator.
Because I cannot speak English so much, it takes time to answer.
Required fields are marked *.

※Please enter more than 5 characters only alphabets.
※Edit or delete are possible for 2000 days after approval.

*

♠Simplistic Comment User Editable v4.0

♠When visitors leave comments on the site this site collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.
♠This site does not use cookie when visitors leave comments and commenter edit comment.
♠This site uses Akismet to reduce spam. Learn how your comment data is processed.

Comments feed

Trackback URL : https://strix.main.jp/wp-trackback.php?p=169847

Sanbanse Funabashi

Top

スクロールさせるか画像をクリックすると元に戻ります。