为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>
问题根源
- start_el中的return打断层级处理:对所有子菜单项和首页项直接执行
return,导致Walker跳过后续所有层级检测逻辑,end_lvl自然不会触发。 - 手动生成子菜单绕过Walker机制:通过
renderChildrenDropdownHtml直接输出子菜单的<ul>结构,没有使用Walker原生的start_lvl/end_lvl处理层级,Walker无法识别需要关闭的层级。 - 子项获取方式错误:
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
相关产品推荐
相关产品推荐

