You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为BS5导航栏自定义walker_nav_menu时end_lvl函数未触发

WordPress BS5导航Walker中end_lvl不触发的问题修复

我在为WordPress主题开发Bootstrap 5主导航栏时,自定义了Walker_Nav_Menu子类BS5_MainMenu,但end_lvl函数始终不触发,无法在菜单列表中添加语言切换的额外元素。以下是我的代码:

原functions.php代码

class BS5_MainMenu extends Walker_Nav_Menu
{

  public function start_el(&$output, $data_object, $depth = 0, $args = null, $current_object_id = 0)
  {
    $item = $data_object; // use more descriptive name for use within this method.

    $frontpageID = get_option('page_on_front');

    if ($item->post_parent != 0 || $item->ID == $frontpageID) {
      return;
    }

    $itemUrl = get_permalink($item);

    $children = $this->getChildren($item->ID);

    $_liCssClass = "nav-item";
    $_aCssClass = "nav-link";

    if (count($children)) {
      $_liCssClass .= " dropdown";
      $_aCssClass .= " dropdown-toggle";
    }

    $output .= '<li class="' .  $_liCssClass . '">';

    if (!empty($itemUrl) && $itemUrl != '#') {
      $output .= '<a href="' . $itemUrl . '" class="' . $_aCssClass . '" role="button" data-bs-toggle="dropdown" aria-expanded="false">';
    } else {
      $output .= '<span>';
    }

    $output .= $item->post_title;

    if (!empty($itemUrl) && $itemUrl != '#') {
      $output .= '</a>';
    } else {
      $output .= '</span>';
    }

    $output .= $this->renderChildrenDropdownHtml($item->ID);
  }

  public function end_lvl(&$output, $depth = 0, $args = null)
  {
    $otherLanguage = $this->get_other_languages()[0];

    $output .= '<li class="nav-item mx-4">';
    $output .= '<a class="nav-link nav-translate" href="' . $otherLanguage->permalink . '">' . $otherLanguage->res_label . ' <span>' . $otherLanguage->displaylanguage . '</span></a>';
    $output .= '</li></ul>';
  }

  private function get_other_languages()
  {
    $_all = get_available_languages();
    $_other = array();
    $_current = get_locale();

    foreach ($_all as $result) {
      if ($_current == $result) {
        continue;
      }

      $_lang_of_result = explode("_", $result)[0];

      $_obj = new stdClass();
      $_obj->displaylanguage = Locale::getDisplayLanguage($_lang_of_result, $_lang_of_result);
      $_obj->permalink = get_msls_permalink($result);
      $_obj->flagurl = get_msls_flag_url($result);
      $_obj->res_label = $_lang_of_result == "da" ? "Skift til" : "Switch to";

      $_other[] = $_obj;
    }

    return $_other;
  }

  private function renderChildrenDropdownHtml($post_id)
  {
    if (!$post_id) {
      return false;
    }

    $children = $this->getChildren($post_id);
    if (!count($children)) {
      return false;
    }

    $html = '<ul class="dropdown-menu">';

    foreach ($children as $child) {
      $html .= '<li><a class="dropdown-item" href="' . get_permalink($child) . '">' . $child->post_title . '</a></li>';
    }

    $html .= '</ul>';
    return $html;
  }

  private function getChildren($post_id)
  {
    if (!$post_id) {
      return false;
    }

    $args = array(
      'post_parent' => $post_id,
      'post_type' => 'any',
      'numberposts' => -1,
      'post_status' => 'any'
    );
    return get_children($args);
  }
}

原parts/header.php代码

<nav class="navbar navbar-main navbar-expand-lg highlighted" data-bs-theme="dark">
    <div class="container-fluid">
      <a class="navbar-brand" href="/index.html">
        <img src="<?php echo esc_url(get_template_directory_uri() . '/img/logo.svg'); ?>" height="70" />
      </a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar"
        aria-controls="main-navbar" aria-expanded="false" aria-label="Visning af navigation">
        <span class="navbar-toggler-icon"></span>
      </button>

      <div class="collapse navbar-collapse" id="main-navbar">
        <?php

        wp_nav_menu([
          'theme_location' => 'MainMenu',
          'menu_class' => 'navbar-nav ms-auto mb-2 mb-lg-0',
          'container' => 'ul',
          'walker' => new BS5_MainMenu()
        ]);

        ?>
      </div>
    </div>
  </nav>

问题根源

  1. start_el中的return打断层级处理:对所有子菜单项和首页项直接执行return,导致Walker跳过后续所有层级检测逻辑,end_lvl自然不会触发。
  2. 手动生成子菜单绕过Walker机制:通过renderChildrenDropdownHtml直接输出子菜单的<ul>结构,没有使用Walker原生的start_lvl/end_lvl处理层级,Walker无法识别需要关闭的层级。
  3. 子项获取方式错误:getChildren用get_children()获取子项,这个函数是针对WordPress文章/页面的,而非导航菜单的子菜单项。

修复方案

1. 修改Walker类代码

让Walker遵循原生层级处理流程,同时正确集成语言切换元素:

class BS5_MainMenu extends Walker_Nav_Menu
{
    public function start_el(&$output, $data_object, $depth = 0, $args = null, $current_object_id = 0)
    {
        $item = $data_object;
        $frontpageID = get_option('page_on_front');

        // 首页项直接跳过,子菜单交给Walker原生处理
        if ($item->ID == $frontpageID) {
            return;
        }

        $_liCssClass = "nav-item";
        $_aCssClass = "nav-link";

        // 用Walker自带属性检测子项,替代自定义getChildren
        if (!empty($args->walker->has_children)) {
            $_liCssClass .= " dropdown";
            $_aCssClass .= " dropdown-toggle";
        }

        // 处理子菜单的样式
        if ($depth > 0) {
            $_liCssClass = "dropdown-item";
            $_aCssClass = "";
        }

        $output .= '<li class="' . esc_attr($_liCssClass) . '">';

        $itemUrl = !empty($item->url) ? $item->url : '#';
        $itemUrl = esc_url($itemUrl);

        if ($depth == 0 && !empty($itemUrl) && $itemUrl != '#') {
            $output .= '<a href="' . $itemUrl . '" class="' . esc_attr($_aCssClass) . '" role="button" data-bs-toggle="dropdown" aria-expanded="false">';
        } elseif ($depth > 0 && !empty($itemUrl) && $itemUrl != '#') {
            $output .= '<a href="' . $itemUrl . '" class="' . esc_attr($_aCssClass) . '">';
        } else {
            $output .= '<span>';
        }

        $output .= apply_filters('the_title', $item->title, $item->ID);

        if (!empty($itemUrl) && $itemUrl != '#') {
            $output .= '</a>';
        } else {
            $output .= '</span>';
        }
    }

    public function start_lvl(&$output, $depth = 0, $args = null)
    {
        // 输出BS5规范的子菜单容器
        $output .= '<ul class="dropdown-menu">';
    }

    public function end_lvl(&$output, $depth = 0, $args = null)
    {
        // 在子菜单末尾添加语言切换项
        $otherLanguage = $this->get_other_languages()[0] ?? null;
        if ($otherLanguage) {
            $output .= '<li class="nav-item mx-4">';
            $output .= '<a class="nav-link nav-translate" href="' . esc_url($otherLanguage->permalink) . '">' . esc_html($otherLanguage->res_label) . ' <span>' . esc_html($otherLanguage->displaylanguage) . '</span></a>';
            $output .= '</li>';
        }
        $output .= '</ul>';
    }

    public function end_el(&$output, $data_object, $depth = 0, $args = null)
    {
        // 关闭li标签,保证HTML结构完整
        $output .= '</li>';
    }

    private function get_other_languages()
    {
        $_all = get_available_languages();
        $_other = array();
        $_current = get_locale();

        foreach ($_all as $result) {
            if ($_current == $result) {
                continue;
            }

            $_lang_of_result = explode("_", $result)[0];

            $_obj = new stdClass();
            $_obj->displaylanguage = Locale::getDisplayLanguage($_lang_of_result, $_lang_of_result);
            $_obj->permalink = get_msls_permalink($result);
            $_obj->flagurl = get_msls_flag_url($result);
            $_obj->res_label = $_lang_of_result == "da" ? "Skift til" : "Switch to";

            $_other[] = $_obj;
        }

        return $_other;
    }
}

2. 修改header.php中的菜单调用

移除多余的容器,避免破坏BS5结构:

<nav class="navbar navbar-main navbar-expand-lg highlighted" data-bs-theme="dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="<?php echo esc_url(home_url('/')); ?>">
            <img src="<?php echo esc_url(get_template_directory_uri() . '/img/logo.svg'); ?>" height="70" />
        </a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar"
            aria-controls="main-navbar" aria-expanded="false" aria-label="Visning af navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="main-navbar">
            <?php
            wp_nav_menu([
                'theme_location' => 'MainMenu',
                'menu_class' => 'navbar-nav ms-auto mb-2 mb-lg-0',
                'container' => false, // 关闭默认容器,避免多余div
                'walker' => new BS5_MainMenu()
            ]);
            ?>
        </div>
    </div>
</nav>

关键修改说明

  • 移除手动生成子菜单的逻辑,让Walker通过start_lvl/end_lvl处理层级,确保end_lvl被触发。
  • 使用Walker自带的$args->walker->has_children检测子项,替代错误的getChildren方法。
  • 实现end_el方法保证HTML标签闭合,添加安全函数esc_attr/esc_url/esc_html防止XSS漏洞。

内容的提问来源于stack exchange,提问作者Allan Hougaard Kleist

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:09:53