菜单也是wp_post的对象,首先给菜单添加字段:
添加字段:
add_action('wp_nav_menu_item_custom_fields', 'add_mega_menu_type_field', 10, 4);
function add_mega_menu_type_field($item_id, $item, $depth, $args) {
$mega_menu_type = get_post_meta($item_id, '_mega_menu_type', true);
?>
<p class="field-mega-menu-type description description-wide">
<label for="edit-menu-item-mega-menu-type-<?php echo $item_id; ?>">
<?php _e('Mega Menu Type'); ?><br />
<select id="edit-menu-item-mega-menu-type-<?php echo $item_id; ?>"
name="menu-item-mega-menu-type[<?php echo $item_id; ?>]">
<option value="" <?php selected($mega_menu_type, ''); ?>><?php _e('None'); ?></option>
<option value="product" <?php selected($mega_menu_type, 'product'); ?>><?php _e('Product'); ?></option>
<option value="category" <?php selected($mega_menu_type, 'category'); ?>><?php _e('Category'); ?></option>
<option value="post" <?php selected($mega_menu_type, 'post'); ?>><?php _e('Post'); ?></option>
</select>
</label>
</p>
<?php
}
保存字段:
/**
* Step 2: Save Custom Menu Field
*/
add_action('wp_update_nav_menu_item', 'save_mega_menu_type_field', 10, 3);
function save_mega_menu_type_field($menu_id, $menu_item_db_id, $menu_item_args) {
if (isset($_POST['menu-item-mega-menu-type'][$menu_item_db_id])) {
$mega_menu_type = sanitize_text_field($_POST['menu-item-mega-menu-type'][$menu_item_db_id]);
update_post_meta($menu_item_db_id, '_mega_menu_type', $mega_menu_type);
}
}
用字段控制输出
首先要在class里面定义属性:
protected $current_item = null;
protected $current_item_ancestors = array();
下面这个函数很重要,在class里面追踪字段: 没有就无法给class的属性赋于字段值
/**
* Track the current item before displaying it
*/
public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) {
// Store current item
$this->current_item = $element;
// Store ancestors for this item
$this->current_item_ancestors = array();
if (isset($element->menu_item_parent) && $element->menu_item_parent > 0) {
$ancestors = array();
$ancestor_id = $element->menu_item_parent;
while ($ancestor_id > 0) {
foreach ($children_elements as $children) {
foreach ($children as $child) {
if ($child->ID == $ancestor_id) {
$ancestors[] = $child;
$ancestor_id = $child->menu_item_parent;
continue 2;
}
}
$ancestor_id = 0;
}
}
$this->current_item_ancestors = array_reverse($ancestors);
}
parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
}
用字段来控制二级ul的输出方式:
$mega_menu_type = '';
// Get mega menu type from parent item
if (!empty($this->current_item_ancestors)) {
$parent_item = end($this->current_item_ancestors);
$mega_menu_type = get_post_meta($parent_item->ID, '_mega_menu_type', true);
} elseif ($this->current_item) {
$mega_menu_type = get_post_meta($this->current_item->ID, '_mega_menu_type', true);
}
if ($depth === 0) {
switch ($mega_menu_type) {
case 'product':
$output .= '<div class="mega-menu mega-menu-products-container">';
break;
case 'category':
$output .= '<div class="mega-menu mega-menu-categories-container">';
break;
case 'post':
$output .= '<div class="mega-menu mega-menu-posts-container">';
break;
default:
$output .= '<ul class="sub-menu">';
break;
}
} else {
// For nested levels
switch ($mega_menu_type) {
case 'product':
$output .= '<div class="mega-menu-product-submenu">';
break;
case 'category':
$output .= '<div class="mega-menu-category-submenu">';
break;
case 'post':
$output .= '<div class="mega-menu-post-submenu">';
break;
default:
$output .= '<ul class="sub-menu">';
break;
}
}
end_lv1 里面同样操作一遍:
$mega_menu_type = '';
// Get mega menu type from parent item
if (!empty($this->current_item_ancestors)) {
$parent_item = end($this->current_item_ancestors);
$mega_menu_type = get_post_meta($parent_item->ID, '_mega_menu_type', true);
} elseif ($this->current_item) {
$mega_menu_type = get_post_meta($this->current_item->ID, '_mega_menu_type', true);
}
if ($mega_menu_type && ($mega_menu_type === 'product' || $mega_menu_type === 'category' || $mega_menu_type === 'post')) {
$output .= '</div>';
} else {
$output .= '</ul>';
}
start_el1 里面用 output变量重写内容:
$mega_menu_type = get_post_meta($item->ID, '_mega_menu_type', true);
$parent_mega_type = '';
// Get parent mega menu type
if ($depth > 0 && !empty($this->current_item_ancestors)) {
$parent_item = end($this->current_item_ancestors);
$parent_mega_type = get_post_meta($parent_item->ID, '_mega_menu_type', true);
}
// Classes
$classes = empty($item->classes) ? array() : (array) $item->classes;
if ($mega_menu_type && $depth === 0) {
$classes[] = 'has-mega-menu';
$classes[] = 'mega-menu-' . $mega_menu_type;
}
$class_names = join(' ', apply_filters('nav_menu_css_class', array_filter($classes), $item, $args, $depth));
$class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';
// Determine container element
$container_element = 'li';
if ($depth > 0 && $parent_mega_type) {
switch ($parent_mega_type) {
case 'product':
$container_element = 'div';
$class_names = ' class="mega-product-item"';
break;
case 'category':
$container_element = 'div';
$class_names = ' class="mega-category-item"';
break;
case 'post':
$container_element = 'div';
$class_names = ' class="mega-post-item"';
break;
}
}
$output .= '<' . $container_element . $class_names . '>';
// Build menu item
$atts = array();
$atts['title'] = !empty($item->attr_title) ? $item->attr_title : '';
$atts['target'] = !empty($item->target) ? $item->target : '';
$atts['rel'] = !empty($item->xfn) ? $item->xfn : '';
$atts['href'] = !empty($item->url) ? $item->url : '';
$attributes = '';
foreach ($atts as $attr => $value) {
if (!empty($value)) {
$attributes .= ' ' . $attr . '="' . esc_attr($value) . '"';
}
}
$title = apply_filters('the_title', $item->title, $item->ID);
$item_output = $args->before;
// Generate content based on type
if ($depth > 0 && $parent_mega_type) {
switch ($parent_mega_type) {
case 'product':
$item_output .= $this->get_product_item_content($item);
break;
case 'post':
$item_output .= $this->get_post_item_content($item);
break;
default:
$item_output .= '<a' . $attributes . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
}
} else {
$item_output .= '<a' . $attributes . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
}
$item_output .= $args->after;
$output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args);
}
用不同的函数来加载不同内容:
/**
* Helper method for product content
*/
private function get_product_item_content($item) {
$output = '<div class="mega-product-content">';
if (has_post_thumbnail($item->object_id)) {
$output .= get_the_post_thumbnail($item->object_id, 'thumbnail');
}
$output .= '<h4><a href="' . get_permalink($item->object_id) . '">' . $item->title . '</a></h4>';
if (function_exists('get_woocommerce_price')) {
$product = wc_get_product($item->object_id);
if ($product) {
$output .= '<span class="price">' . $product->get_price_html() . '</span>';
}
}
$output .= '</div>';
return $output;
}
/**
* Helper method for post content
*/
private function get_post_item_content($item) {
$output = '<div class="mega-post-content">';
if (has_post_thumbnail($item->object_id)) {
$output .= get_the_post_thumbnail($item->object_id, 'thumbnail');
}
$output .= '<h4><a href="' . get_permalink($item->object_id) . '">' . $item->title . '</a></h4>';
$output .= '<p>' . wp_trim_words(get_post_field('post_excerpt', $item->object_id), 10) . '</p>';
$output .= '</div>';
return $output;
}
完整版
<?php
class Mega_Menu_Walker extends Walker_Nav_Menu {
// Add properties to track current item and its ancestors
protected $current_item = null;
protected $current_item_ancestors = array();
/**
* Track the current item before displaying it
*/
public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) {
// Store current item
$this->current_item = $element;
// Store ancestors for this item
$this->current_item_ancestors = array();
if (isset($element->menu_item_parent) && $element->menu_item_parent > 0) {
$ancestors = array();
$ancestor_id = $element->menu_item_parent;
while ($ancestor_id > 0) {
foreach ($children_elements as $children) {
foreach ($children as $child) {
if ($child->ID == $ancestor_id) {
$ancestors[] = $child;
$ancestor_id = $child->menu_item_parent;
continue 2;
}
}
$ancestor_id = 0;
}
}
$this->current_item_ancestors = array_reverse($ancestors);
}
parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
}
/**
* Start level based on mega menu type
*/
public function start_lvl(&$output, $depth = 0, $args = null) {
$mega_menu_type = '';
// Get mega menu type from parent item
if (!empty($this->current_item_ancestors)) {
$parent_item = end($this->current_item_ancestors);
$mega_menu_type = get_post_meta($parent_item->ID, '_mega_menu_type', true);
} elseif ($this->current_item) {
$mega_menu_type = get_post_meta($this->current_item->ID, '_mega_menu_type', true);
}
if ($depth === 0) {
switch ($mega_menu_type) {
case 'product':
$output .= '<div class="mega-menu mega-menu-products-container">';
break;
case 'category':
$output .= '<div class="mega-menu mega-menu-categories-container">';
break;
case 'post':
$output .= '<div class="mega-menu mega-menu-posts-container">';
break;
default:
$output .= '<ul class="sub-menu">';
break;
}
} else {
// For nested levels
switch ($mega_menu_type) {
case 'product':
$output .= '<div class="mega-menu-product-submenu">';
break;
case 'category':
$output .= '<div class="mega-menu-category-submenu">';
break;
case 'post':
$output .= '<div class="mega-menu-post-submenu">';
break;
default:
$output .= '<ul class="sub-menu">';
break;
}
}
}
/**
* End level based on mega menu type
*/
public function end_lvl(&$output, $depth = 0, $args = null) {
$mega_menu_type = '';
// Get mega menu type from parent item
if (!empty($this->current_item_ancestors)) {
$parent_item = end($this->current_item_ancestors);
$mega_menu_type = get_post_meta($parent_item->ID, '_mega_menu_type', true);
} elseif ($this->current_item) {
$mega_menu_type = get_post_meta($this->current_item->ID, '_mega_menu_type', true);
}
if ($mega_menu_type && ($mega_menu_type === 'product' || $mega_menu_type === 'category' || $mega_menu_type === 'post')) {
$output .= '</div>';
} else {
$output .= '</ul>';
}
}
/**
* Start element with proper container
*/
public function start_el(&$output, $item, $depth = 0, $args = null, $id = 0) {
$mega_menu_type = get_post_meta($item->ID, '_mega_menu_type', true);
$parent_mega_type = '';
// Get parent mega menu type
if ($depth > 0 && !empty($this->current_item_ancestors)) {
$parent_item = end($this->current_item_ancestors);
$parent_mega_type = get_post_meta($parent_item->ID, '_mega_menu_type', true);
}
// Classes
$classes = empty($item->classes) ? array() : (array) $item->classes;
if ($mega_menu_type && $depth === 0) {
$classes[] = 'has-mega-menu';
$classes[] = 'mega-menu-' . $mega_menu_type;
}
$class_names = join(' ', apply_filters('nav_menu_css_class', array_filter($classes), $item, $args, $depth));
$class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';
// Determine container element
$container_element = 'li';
if ($depth > 0 && $parent_mega_type) {
switch ($parent_mega_type) {
case 'product':
$container_element = 'div';
$class_names = ' class="mega-product-item"';
break;
case 'category':
$container_element = 'div';
$class_names = ' class="mega-category-item"';
break;
case 'post':
$container_element = 'div';
$class_names = ' class="mega-post-item"';
break;
}
}
$output .= '<' . $container_element . $class_names . '>';
// Build menu item
$atts = array();
$atts['title'] = !empty($item->attr_title) ? $item->attr_title : '';
$atts['target'] = !empty($item->target) ? $item->target : '';
$atts['rel'] = !empty($item->xfn) ? $item->xfn : '';
$atts['href'] = !empty($item->url) ? $item->url : '';
$attributes = '';
foreach ($atts as $attr => $value) {
if (!empty($value)) {
$attributes .= ' ' . $attr . '="' . esc_attr($value) . '"';
}
}
$title = apply_filters('the_title', $item->title, $item->ID);
$item_output = $args->before;
// Generate content based on type
if ($depth > 0 && $parent_mega_type) {
switch ($parent_mega_type) {
case 'product':
$item_output .= $this->get_product_item_content($item);
break;
case 'post':
$item_output .= $this->get_post_item_content($item);
break;
default:
$item_output .= '<a' . $attributes . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
}
} else {
$item_output .= '<a' . $attributes . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
}
$item_output .= $args->after;
$output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args);
}
/**
* End element with proper container
*/
public function end_el(&$output, $item, $depth = 0, $args = null) {
$parent_mega_type = '';
if ($depth > 0 && !empty($this->current_item_ancestors)) {
$parent_item = end($this->current_item_ancestors);
$parent_mega_type = get_post_meta($parent_item->ID, '_mega_menu_type', true);
}
$container_element = 'li';
if ($depth > 0 && in_array($parent_mega_type, ['product', 'category', 'post'])) {
$container_element = 'div';
}
$output .= "</{$container_element}>";
}
/**
* Helper method for product content
*/
private function get_product_item_content($item) {
$output = '<div class="mega-product-content">';
if (has_post_thumbnail($item->object_id)) {
$output .= get_the_post_thumbnail($item->object_id, 'thumbnail');
}
$output .= '<h4><a href="' . get_permalink($item->object_id) . '">' . $item->title . '</a></h4>';
if (function_exists('get_woocommerce_price')) {
$product = wc_get_product($item->object_id);
if ($product) {
$output .= '<span class="price">' . $product->get_price_html() . '</span>';
}
}
$output .= '</div>';
return $output;
}
/**
* Helper method for post content
*/
private function get_post_item_content($item) {
$output = '<div class="mega-post-content">';
if (has_post_thumbnail($item->object_id)) {
$output .= get_the_post_thumbnail($item->object_id, 'thumbnail');
}
$output .= '<h4><a href="' . get_permalink($item->object_id) . '">' . $item->title . '</a></h4>';
$output .= '<p>' . wp_trim_words(get_post_field('post_excerpt', $item->object_id), 10) . '</p>';
$output .= '</div>';
return $output;
}
}