Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

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:

<ul>
    <li><a href="#">Example</a></li>
    <li class="widget">
        <a href="#">Example</a>
        <ul>
            <li><a href="#">Example</a></li>
            <li><a href="#">Example</a></li>
            <li><a href="#">Example</a></li>
            <li class="break"><a href="#">Example</a></li>
            <li><a href="#">Example</a></li>
            <li><a href="#">Example</a></li>
        </ul>
    </li>
    <li><a href="#">Example</a></li>
    <li><a href="#">Example</a></li>
    <li><a href="#">Example</a></li>
</ul>

With break and widget being classes added by the user in the WordPress menu editor, then the walker and function output this:

<ul>
    <li><a href="#">Example</a></li>
    <li class="widget mega-menu-columns-3">
        <a href="#">Example</a>
        <section>
            <ul>
                <li><a href="#">Example</a></li>
                <li><a href="#">Example</a></li>
                <li><a href="#">Example</a></li>
            </ul>
            <ul class="sub-menu">
                <li class="break"><a href="#">Example</a></li>
                <li><a href="#">Example</a></li>
                <li><a href="#">Example</a></li>
            </ul>
            <aside>
                <h1>Widget Title</h1>
                <p>Widget text</p>
            </aside>
        </section>
    </li>
    <li><a href="#">Example</a></li>
    <li><a href="#">Example</a></li>
    <li><a href="#">Example</a></li>
</ul>

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
share|improve this question
1  
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 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. –  Rev Jun 23 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 at 13:43
    
Can you add comments explaining what each if statement in start_el is doing? –  Tom J Nowell Jun 23 at 13:45
1  
Alright, added all the comments. –  Rev Jun 23 at 14:02

1 Answer 1

up vote 2 down vote accepted

If all of it is your own code then it would be reasonable to just implement that as part of walker.

If you need to add a filter while third party walker runs you could probably:

Add it at wp_nav_menu_args hook, checking if walker is set to that:

add_filter( 'wp_nav_menu_args', function( $args ) {

    if ( $args['walker'] instanceof megaMenuWalker ) {

        add_filter( 'wp_nav_menu_objects', 'callback_name_here' );
    }

    return $args;
} );

Remove it at wp_nav_menu hook after the menu markup is completely generated:

add_filter( 'wp_nav_menu', function( $nav_menu ) {

    remove_filter( 'wp_nav_menu_objects', 'callback_name_here' );

    return $nav_menu;
} );
share|improve this answer
    
I'm not very good with hooks, I'm surprised I figured out the walker stuff. Can you post an example of how I could do this? –  Rev Jun 23 at 14:03
    
Thank you! Only thing is this doesn't share the variable, but I think I can figure out how to make a public variable or something on my own. –  Rev Jun 23 at 14:13

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.