問題描述

我在 WP Admin 中定義了一個選單,如下所示:

我想要在父母頁面上顯示側欄上的所有子連結。例如,如果使用者在我的 「關於我們」 頁面上,我想要一個以綠色突出顯示的 4 個連結的列表顯示在側欄上。

我檢視了 wp_nav_menu()的檔案,它似乎沒有任何 built-in 方式來指定給定選單的特定節點作為生成連結的起點。

我為 a similar situation 建立了一個解決方案,它依賴於頁面父代建立的關係,但我正在尋找一個專門使用選單系統的解決方案。任何幫助將不勝感激。

最佳解決方案

這仍然在我的腦海裡,所以我重新審視了這個解決方案,而不是依賴於上下文:

add_filter( 'wp_nav_menu_objects', 'submenu_limit', 10, 2 );

function submenu_limit( $items, $args ) {

    if ( empty( $args->submenu ) ) {
        return $items;
    }

    $ids       = wp_filter_object_list( $items, array( 'title' => $args->submenu ), 'and', 'ID' );
    $parent_id = array_pop( $ids );
    $children  = submenu_get_children_ids( $parent_id, $items );

    foreach ( $items as $key => $item ) {

        if ( ! in_array( $item->ID, $children ) ) {
            unset( $items[$key] );
        }
    }

    return $items;
}

function submenu_get_children_ids( $id, $items ) {

    $ids = wp_filter_object_list( $items, array( 'menu_item_parent' => $id ), 'and', 'ID' );

    foreach ( $ids as $id ) {

        $ids = array_merge( $ids, submenu_get_children_ids( $id, $items ) );
    }

    return $ids;
}

Usage

$args = array(
    'theme_location' => 'slug-of-the-menu', // the one used on register_nav_menus
    'submenu' => 'About Us', // could be used __() for translations
);

wp_nav_menu( $args );

次佳解決方案

嗨 @jessegavin:

導航選單儲存在自定義帖子型別和自定義分類法的組閤中。每個選單儲存為自定義分類法 (即 wp_term_taxonomy 中的 nav_menu) 中的術語 (即 「關於選單」,位於 wp_terms 中) 。

每個導航選單專案都儲存為 post_type=='nav_menu_item'(即 wp_posts 中的 「關於公司」(wp_posts)),其屬性以_menu_item_* _menu_item_*meta_key 字首儲存為後期後設資料 (wp_postmeta),其中_menu_item_menu_item_parent 是選單項的父級 ID Nav 選單專案帖子。

選單和選單項之間的關係儲存在 wp_term_relationships 中,其中 object_id 與 Nav 選單項的 $post->ID 相關,$term_relationships->term_taxonomy_idwp_term_taxonomywp_terms 中統一定義的選單有關。

我很確定可以鉤住'wp_update_nav_menu''wp_update_nav_menu_item'wp_terms 中建立實際選單,並在 wp_term_taxonomywp_term_relationships 中建立一個並行的關係,其中每個具有 sub-Nav 選單項的 Nav 選單項也成為自己的 Nav 選單。

你也想鉤住'wp_get_nav_menus'(根據我幾個月前所做的類似工作,我建議將其新增到 WP 3.0),以確保您的生成的導航選單不會被管理員使用者手動顯示,否則他們會很快失去同步,那麼你手上會有一個資料噩夢。

聽起來像一個有趣和有用的專案,但是它比現在可以承受的更多的程式碼和測試,部分原因是因為同步資料的任何東西往往是一個 PITA 來解決所有的錯誤 (和因為付費客戶正在迫使我完成工作。:) 但是配備上面的資訊我很有動力的 WordPress 外掛開發人員可以編寫它,如果他們想。

當然,你現在意識到,如果你做程式碼,你有義務把它發回到這裡,所以我們都可以從你的慷慨中受益! 🙂

第三種解決方案

@goldenapples:Your Walker Class 不起作用但這個想法真的很好。我根據你的想法創造了一個步行者:

class Selective_Walker extends Walker_Nav_Menu
{
    function walk( $elements, $max_depth) {

        $args = array_slice(func_get_args(), 2);
        $output = '';

        if ($max_depth < -1) //invalid parameter
            return $output;

        if (empty($elements)) //nothing to walk
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // flat display
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * need to display in hierarchical order
         * separate elements into two buckets: top level and children elements
         * children_elements is two dimensional array, eg.
         * children_elements[10][] contains all sub-elements whose parent is 10.
         */
        $top_level_elements = array();
        $children_elements  = array();
        foreach ( $elements as $e) {
            if ( 0 == $e->$parent_field )
                $top_level_elements[] = $e;
            else
                $children_elements[ $e->$parent_field ][] = $e;
        }

        /*
         * when none of the elements is top level
         * assume the first one must be root of the sub elements
         */
        if ( empty($top_level_elements) ) {

            $first = array_slice( $elements, 0, 1 );
            $root = $first[0];

            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( $root->$parent_field == $e->$parent_field )
                    $top_level_elements[] = $e;
                else
                    $children_elements[ $e->$parent_field ][] = $e;
            }
        }

        $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );  //added by continent7
        foreach ( $top_level_elements as $e ){  //changed by continent7
            // descend only on current tree
            $descend_test = array_intersect( $current_element_markers, $e->classes );
            if ( !empty( $descend_test ) )
                $this->display_element( $e, $children_elements, 2, 0, $args, $output );
        }

        /*
         * if we are displaying all levels, and remaining children_elements is not empty,
         * then we got orphans, which should be displayed regardless
         */
         /* removed by continent7
        if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
            $empty_array = array();
            foreach ( $children_elements as $orphans )
                foreach( $orphans as $op )
                    $this->display_element( $op, $empty_array, 1, 0, $args, $output );
         }
        */
         return $output;
    }
}

現在你可以使用:

<?php wp_nav_menu(
   array(
       'theme_location'=>'test',
       'walker'=>new Selective_Walker() )
   ); ?>

輸出是包含當前根元素的列表,它是孩子 (而不是他們的孩子) 。 Def:根元素:=與當前頁面對應的頂級選單項,或當前頁面的父級或父級父級的頂級選單項

這並不完全是回答原來的問題,而是差不多,因為還有頂級的專案。這對我來說很好,因為我想要頂級元素作為側欄的標題。如果要擺脫這一點,您可能需要重寫 display_element 或使用 HTML-Parser 。

第四種方案

這是一個步行者擴充套件,應該做你想要的:

class Selective_Walker extends Walker_Nav_Menu
{

    function walk( $elements, $max_depth) {

        $args = array_slice(func_get_args(), 2);
        $output = '';

        if ($max_depth < -1) //invalid parameter
            return $output;

        if (empty($elements)) //nothing to walk
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // flat display
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * need to display in hierarchical order
         * separate elements into two buckets: top level and children elements
         * children_elements is two dimensional array, eg.
         * children_elements[10][] contains all sub-elements whose parent is 10.
         */
        $top_level_elements = array();
        $children_elements  = array();
        foreach ( $elements as $e) {
            if ( 0 == $e->$parent_field )
                $top_level_elements[] = $e;
            else
                $children_elements[ $e->$parent_field ][] = $e;
        }

        /*
         * when none of the elements is top level
         * assume the first one must be root of the sub elements
         */
        if ( empty($top_level_elements) ) {

            $first = array_slice( $elements, 0, 1 );
            $root = $first[0];

            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( $root->$parent_field == $e->$parent_field )
                    $top_level_elements[] = $e;
                else
                    $children_elements[ $e->$parent_field ][] = $e;
            }
        }

        $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );

        foreach ( $top_level_elements as $e ) {

            // descend only on current tree
            $descend_test = array_intersect( $current_element_markers, $e->classes );
            if ( empty( $descend_test ) )  unset ( $children_elements );

            $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
        }

        /*
         * if we are displaying all levels, and remaining children_elements is not empty,
         * then we got orphans, which should be displayed regardless
         */
        if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
            $empty_array = array();
            foreach ( $children_elements as $orphans )
                foreach( $orphans as $op )
                    $this->display_element( $op, $empty_array, 1, 0, $args, $output );
         }

         return $output;
    }

}

基於我以前在我的評論中引用的 mfields 程式碼。它所做的一切就是檢查選單中的步驟來檢視當前的元素是否是 (1) 當前選單項,或者 (2) 當前選單項的祖先,並且只有當這些條件中的任何一個為真。希望這適合你。

要使用它,只需在呼叫選單時新增一個”walker” 引數,即:

<?php wp_nav_menu(
   array(
       'theme_location'=>'test',
       'walker'=>new Selective_Walker() )
   ); ?>

第五種方案

我為自己整理下列課程。它會找到當前頁面的頂級導航父母,或者您可以在 walker 建構函式中給它一個目標頂部的導航 ID 。

class Walker_SubNav_Menu extends Walker_Nav_Menu {
    var $target_id = false;

    function __construct($target_id = false) {
        $this->target_id = $target_id;
    }

    function walk($items, $depth) {
        $args = array_slice(func_get_args(), 2);
        $args = $args[0];
        $parent_field = $this->db_fields['parent'];
        $target_id = $this->target_id;
        $filtered_items = array();

        // if the parent is not set, set it based on the post
        if (!$target_id) {
            global $post;
            foreach ($items as $item) {
                if ($item->object_id == $post->ID) {
                    $target_id = $item->ID;
                }
            }
        }

        // if there isn't a parent, do a regular menu
        if (!$target_id) return parent::walk($items, $depth, $args);

        // get the top nav item
        $target_id = $this->top_level_id($items, $target_id);

        // only include items under the parent
        foreach ($items as $item) {
            if (!$item->$parent_field) continue;

            $item_id = $this->top_level_id($items, $item->ID);

            if ($item_id == $target_id) {
                $filtered_items[] = $item;
            }
        }

        return parent::walk($filtered_items, $depth, $args);
    }

    // gets the top level ID for an item ID
    function top_level_id($items, $item_id) {
        $parent_field = $this->db_fields['parent'];

        $parents = array();
        foreach ($items as $item) {
            if ($item->$parent_field) {
                $parents[$item->ID] = $item->$parent_field;
            }
        }

        // find the top level item
        while (array_key_exists($item_id, $parents)) {
            $item_id = $parents[$item_id];
        }

        return $item_id;
    }
}

導航電話:

wp_nav_menu(array(
    'theme_location' => 'main_menu',
    'walker' => new Walker_SubNav_Menu(22), // with ID
));

第六種方案

更新:我把它變成一個外掛。 Download here


我需要自己解決這個問題,並最終捲入對選單查詢結果的過濾器。它允許您正常使用 wp_nav_menu,但是基於父元素的標題來選擇選單的 sub-section 。將 submenu 引數新增到選單中,如下所示:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => 'About Us',
));

你甚至可以透過把斜槓放在幾個深度:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => 'About Us/Board of Directors'
));

或者如果您喜歡使用陣列:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => array('About Us', 'Board of Directors')
));

它使用標題的小插曲版本,這應該使它寬容諸如資本和標點符號之類的東西。

參考文獻

注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。