Is there any way to run a particular add_filter function whenever a specific Walker_Nav_Menu class runs? The function always needs to run when the walker goes, and I'd like them to share variables, in particular $column_limit. The function should run just before all the other stuff in the walker, in order to add classes to the li's before they get generated.

The purpose of this whole set of PHP is the enable mega-menu functionality in WordPress. Essentially, if I have markup like this:

The function is adding mega-menu-columns-# to the <li> in the above code. All the other changes are being made by the Walker. All of this works, but I'd like it to work with less configuration; essentially I want to be able to set the column limit once, and not have to set the theme_location in the function. If I set a menu to use the walker, I want all of my PHP to run automatically. If this isn't clear enough, just ask for further clarification.

Here's my current code:

// mega menu walker
class megaMenuWalker extends Walker_Nav_Menu {
private $column_limit = 3; // used to how many columns can be generated (</ul><ul class="sub-menu"> and the widgets)
private $show_widget = false; // used to determine weather or not to show a widget in the drop down
private $column_count = 0; // for counting how many columns are in a drop down
static $li_count = 0; // for counting how many lis are in a drop down, used only to check if the li is first in its list
function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
$classes = empty($item->classes) ? array() : (array) $item->classes; // retrive array of classes for each li
$item_id = $item->ID; // retrieve item id for each li, not really used, keeping it to maintain standard WP menu structure
if ($depth == 0) {
self::$li_count = 0; // reset the li counter when we return to the top level in the menu
}
if ($depth == 0 && in_array("widget", $classes)) {
$this->show_widget = true; // show the widget if the top level li has a class of "widget"
$this->column_count++; // if the widget is shown, increase the column counter by one
}
if ($depth == 1 && self::$li_count == 1) {
$this->column_count++; // if there's anything in a drop down, increase the column counter by one
}
if ($depth == 1 && in_array("break", $classes) && self::$li_count != 1 && $this->column_count < $this->column_limit) {
// if we're in ul ul, and an li has a class of break, and it's not the first in its list, and we haven't met the column limit...
$output .= "</ul><ul class=\"sub-menu\">"; // add a break in the list
$this->column_count++; // increase the column count by one
}
$class_names = join(" ", apply_filters("nav_menu_css_class", array_filter($classes), $item)); // set up the classes array to be added as classes to each li
$class_names = " class=\"" . esc_attr($class_names) . "\""; // set up class HTML
$output .= sprintf(
"<li id=\"menu-item-%s\"%s><a href=\"%s\">%s</a>", // output li structure
$item_id, // not really used, keeping it for standard WP menu structure
$class_names, // add the class names
$item->url, // add the URL
$item->title // add the title
);
self::$li_count++; // increase the li counter. Not used except if == 1
}
function start_lvl(&$output, $depth = 0, $args = array()) {
if ($depth == 0) {
$output .= "<section>"; // if a drop down exists, wrap it in <section>. I'm not sure why $depth == 0 and not 1, but it works, so I'm not complaining.
}
$output .= "<ul class=\"sub-menu\">"; // output standard WP menu UL for drop downs
}
function end_lvl(&$output, $depth = 0, $args = array()) {
$output .= "</ul>"; // close UL for drop downs
if ($depth == 0) {
if ($this->show_widget) {
// if the parent li has a class of widget...
ob_start();
dynamic_sidebar("Navigation Callout"); // show the widget
$widget = ob_get_contents(); // retrieve the widget's HTML as a variable
ob_end_clean(); // clear the shown widget
$output .= $widget; // show the widget
$this->show_widget = false; // reset the widget variable to false so the next li doesn't get one unless indicated.
// I know this section is complicated, but it was the only way to correctly display the widget
}
$output .= "</section>"; // end the <section> that contains the drop downs.
}
}
function end_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
if ($depth == 0 && $this->column_count > 0) {
$this->column_count = 0; // reset the column counter when a drop down ends.
}
$output .= "</li>"; // close the li of each menu item
}
}
// add mega-menu-columns-# classes
// I didn't write this, but I'll do my best to explain it
add_filter("wp_nav_menu_objects", function($items, $args) {
if ("first" !== $args->theme_location) {
return $items; // don't run the function if it's not a specific menu. This should really be a check if the walker is set to a menu, not the menu's location.
}
static $post_id = 0; // set up the post id variable
static $x_key = 0; // not sure what this is
static $column_count = 1; // set the initial column count to 1
static $column_limit = 3; // set the column limit to 3. Ideally would pull from the walker.
static $li_count = 0; // set up the li counter
$tmp = array(); // not sure waht this is
foreach($items as $key => $item) {
if (0 == $item->menu_item_parent) {
$x_key = $key; // not sure...
$post_id = $item->ID; // set the post ID to the item's ID
$column_count = 1; // reset the column count
$li_count = 0; // reset the li count
if (in_array("widget", $item->classes, 1)) {
$column_count++; // if widget is in the li class, add 1 to the column count
}
}
if ($post_id == $item->menu_item_parent) {
$li_count++; // if an item has children, increase the li count by one
if (in_array("break", $item->classes, 1) && $li_count > 1 && $column_count < $column_limit) {
$column_count++; // if break is in the item's classes, and the li count isn't one, and the column count is'nt equal to the column limit, increase the counter by one
}
$tmp[$x_key] = $column_count; // not sure what this is.
}
}
foreach($tmp as $key => $value) {
$items[$key]->classes[] = sprintf("mega-menu-columns-%d", $value); // adding the new class based on column count to the lis
}
unset($tmp); // not sure
return $items; // ending the filter
}, PHP_INT_MAX, 2); // not sure

You say when a class runs, but which part of the class? There are a lot of lines of code there, and it's not clear exactly what you're trying to do, or why you want to do it. Context is important and you've provided none
–
Tom J Nowell♦Jun 23 '14 at 13:41

I think it would only work if it ran just before everything else, since the filter adds classes to the lis, which get generated in the walker.
–
RevJun 23 '14 at 13:42

If I understood what you were trying to do I might be able to help you, explain your original problem, not how you're trying to implement it
–
Tom J Nowell♦Jun 23 '14 at 13:43

Can you add comments explaining what each if statement in start_el is doing?
–
Tom J Nowell♦Jun 23 '14 at 13:45