wp-bootstrap / wp-bootstrap-navwalker

A custom WordPress nav walker class to fully implement the Twitter Bootstrap 4.0+ navigation style (v3-branch available for Bootstrap 3) in a custom theme using the WordPress built in menu manager.
https://wp-bootstrap.github.io/wp-bootstrap-navwalker/
GNU General Public License v3.0
3.37k stars 1.94k forks source link

A few issues I have noticed #408

Open Sammaye opened 5 years ago

Sammaye commented 5 years ago

This is mostly a sum up of: https://github.com/understrap/understrap/issues/884

As a disclaimer: I have only done a cursory glance of the file, I have not actually downloaded and ran it. I did test the regex out though to reproduce that problem.

  1. Multiple sub menus will have the incorrect ID associated with them due to the regex here https://github.com/wp-bootstrap/wp-bootstrap-navwalker/blob/master/class-wp-bootstrap-navwalker.php#L73 always picking the first sub menu.
  2. It does not skip depths deeper than 1, I noticed in my own testing that the WP nav walker will trigger the start_el and end_el for deeper levels even if you don't have them, I believe it does n+1 levels (I have not double checked that) and this can cause broken HTML
  3. I have also noticed that support for the active sr-only item is not there
  4. Use WP's own $this->has_children instead of overriding this: https://github.com/wp-bootstrap/wp-bootstrap-navwalker/blob/master/class-wp-bootstrap-navwalker.php#L317
  5. Fix up coding standards such as https://github.com/wp-bootstrap/wp-bootstrap-navwalker/blob/master/class-wp-bootstrap-navwalker.php#L119 this isn't like super OMG since PHP does not differentiate between static and dynamic methods but it will still throw a strict error here.
  6. Since you are closing the tags here https://github.com/wp-bootstrap/wp-bootstrap-navwalker/blob/master/class-wp-bootstrap-navwalker.php#L281 this causes end_el to still call and print a </li> which will produce incorrect HTML

I think that's enough to start, there are maybe a few other places but I'll leave it at that for the minute :)

I have attached something I've cooked up as well, this produces valid HTML for most menus, but it lacks some features this plugin possesses, for example: icon type detection and icon only menu items; also it is super unclean and dirty; essentially it is my first draft of converting the BS3 version to BS4:

<?php

/**
 * Class Name: wp_bootstrap_navwalker
 * GitHub URI: https://github.com/twittem/wp-bootstrap-navwalker
 * Description: A custom WordPress nav walker class to implement the Bootstrap 3 navigation style in a custom theme using the WordPress built in menu manager.
 * Version: 2.0.4
 * Author: Edward McIntyre - @twittem
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 */

class Understrap_Walker_Nav_Menu extends Walker_Nav_Menu {

    public $item;

    public $sub_menu_tag = 'div';

    /**
     * Starts the list before the elements are added.
     *
     * @since 3.0.0
     *
     * @see Walker::start_lvl()
     *
     * @param string $output Used to append additional content (passed by reference).
     * @param int $depth Depth of menu item. Used for padding.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     */
    public function start_lvl( &$output, $depth = 0, $args = array() ) {
        if ( 1 < $depth ) {
            return;
        }

        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }
        $indent = str_repeat( $t, $depth );

        // Default class.
        $classes = array( 'dropdown-menu' );

        /**
         * Filters the CSS class(es) applied to a menu list element.
         *
         * @since 4.8.0
         *
         * @param array $classes The CSS classes that are applied to the menu `<ul>` element.
         * @param stdClass $args An object of `wp_nav_menu()` arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $class_names = implode( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );

        $atts                    = array();
        $atts['class']           = $class_names;
        $atts['aria-labelledby'] = 'navbarDropdown-' . $this->item->ID;

        $attributes = $this->html_attributes( $atts );
        $attributes = $attributes ? ' ' . $attributes : '';

        $output .= "{$n}{$indent}<" . $this->sub_menu_tag . "$attributes>{$n}";
    }

    public function end_lvl( &$output, $depth = 0, $args = array() ) {
        if ( 1 < $depth ) {
            return;
        }

        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }
        $indent = str_repeat( $t, $depth );
        $output .= "$indent</" . $this->sub_menu_tag . ">{$n}";
    }

    /**
     * Starts the element output.
     *
     * @since 3.0.0
     * @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
     *
     * @see Walker::start_el()
     *
     * @param string $output Used to append additional content (passed by reference).
     * @param WP_Post $item Menu item data object.
     * @param int $depth Depth of menu item. Used for padding.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     * @param int $id Current item ID.
     */
    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        if ( 1 < $depth ) {
            return;
        }

        $this->item = $item;

        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }
        $indent =  $depth ? str_repeat( $t, $depth ) : '';

        /**
         * Filters the arguments for a single nav menu item.
         *
         * @since 4.4.0
         *
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param WP_Post $item Menu item data object.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );

        $classes   = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;
        if ( 0 === $depth ) {
            $classes[]       = 'nav-item';
            $this->item->tag = 'li';
        } else if ( ! empty( $item->title ) && strcasecmp( $item->title, 'divider' ) === 0 ) {
            $classes[]       = 'dropdown-divider';
            $this->item->tag = 'div';
        } else if ( ! empty( $item->attr_title ) && strcasecmp( $item->attr_title, 'header' ) === 0 ) {
            $classes[]       = 'dropdown-header';
            $this->item->tag = 'div';
        } else {
            $this->item->tag = 'a';
        }

        if ( $this->has_children && 0 === $depth ) {
            $classes[] = 'dropdown';
        }

        $current = false;
        foreach ( $item->classes as $class ) {
            if ( in_array( $class, [
                'current-menu-parent',
                'current-menu-ancestor',
                'current-menu-item',
                'current-page-item'
            ] ) ) {
                $current   = true;
                $classes[] = 'active';
            }
        }

        /**
         * Filters the CSS class(es) applied to a menu item's list item element.
         *
         * @since 3.0.0
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param array $classes The CSS classes that are applied to the menu item's `<li>` element.
         * @param WP_Post $item The current menu item.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $classes = apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth );

        /**
         * Filters the ID applied to a menu item's list item element.
         *
         * @since 3.0.1
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param string $menu_id The ID that is applied to the menu item's `<li>` element.
         * @param WP_Post $item The current menu item.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $id = esc_attr( apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth ) );

        $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 : '';
        $atts['class']  = [];

        if ( $this->has_children && 0 === $depth ) {
            $atts['id']            = 'navbarDropdown-' . $item->ID;
            $atts['role']          = 'button';
            $atts['href']          = '#';
            $atts['data-toggle']   = 'dropdown';
            $atts['class']         = [ 'nav-link', 'dropdown-toggle' ];
            $atts['aria-haspopup'] = 'true';
            $atts['aria-expanded'] = 'false';
        } else if ( 1 === $depth ) {
            $atts['id']    = $id;
            $atts['class'] = array_merge($atts['class'], ['dropdown-item'], $classes);

            if ( $current ) {
                $atts['class'][] = 'active';
            }
        } else {
            $atts['class']  = ['nav-link'];
        }

        $atts['class'] = esc_attr( implode(' ', $atts['class'] ) );

        $id          = $id ? ' id="' . $id . '"' : '';
        $class_names = $classes ? ' class="' . implode( ' ', $classes) . '"' : '';

        /**
         * Filters the HTML attributes applied to a menu item's anchor element.
         *
         * @since 3.6.0
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param array $atts {
         *     The HTML attributes applied to the menu item's `<a>` element, empty strings are ignored.
         *
         * @type string $title Title attribute.
         * @type string $target Target attribute.
         * @type string $rel The rel attribute.
         * @type string $href The href attribute.
         * }
         *
         * @param WP_Post $item The current menu item.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );

        /** This filter is documented in wp-includes/post-template.php */
        $title = apply_filters( 'the_title', $item->title, $item->ID );

        $icon = '';
        if ( ! empty( $item->icon ) ) {
            $icon = '<span class="' . esc_attr( $item->icon ) . '"></span>&nbsp;';
        }

        /**
         * Filters a menu item's title.
         *
         * @since 4.4.0
         *
         * @param string $title The menu item's title.
         * @param WP_Post $item The current menu item.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth );

        if ( $this->item->tag === 'div' ) {
            $item_output = $args->before . $indent . '<div' . $id . $class_names . '>' . $args->after;

            /**
             * Filters a menu item's starting output.
             *
             * The menu item's starting output only includes `$args->before`, the opening `<a>`,
             * the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
             * no filter for modifying the opening and closing `<li>` for a menu item.
             *
             * @since 3.0.0
             *
             * @param string $item_output The menu item's starting HTML output.
             * @param WP_Post $item Menu item data object.
             * @param int $depth Depth of menu item. Used for padding.
             * @param stdClass $args An object of wp_nav_menu() arguments.
             */
            $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

            return;
        }

        $item_output = $args->before;

        if ( 0 === $depth ) {
            $item_output .= $indent . '<li' . $id . $class_names . '>';
        }

        $attributes = '';
        foreach ( $atts as $attr => $value ) {
            if ( ! empty( $value ) ) {
                $value      = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
                $attributes .= ' ' . $attr . '="' . $value . '"';
            }
        }

        $item_output .= '<a' . $attributes . '>' . $icon;
        $item_output .= $args->link_before . $title . $args->link_after;

        if ( $current ) {
            $item_output .= '<span class="sr-only">(current)</span>';
        }

        if ( $this->has_children && 0 === $depth ) {
            $item_output .= ' <span class="caret"></span>';
        }

        if ( 0 === $depth ) {
            $item_output .= '</a>';
        }

        $item_output .= $args->after;

        /**
         * Filters a menu item's starting output.
         *
         * The menu item's starting output only includes `$args->before`, the opening `<a>`,
         * the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
         * no filter for modifying the opening and closing `<li>` for a menu item.
         *
         * @since 3.0.0
         *
         * @param string $item_output The menu item's starting HTML output.
         * @param WP_Post $item Menu item data object.
         * @param int $depth Depth of menu item. Used for padding.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         */
        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }

    /**
     * Ends the element output, if needed.
     *
     * @since 3.0.0
     *
     * @see Walker::end_el()
     *
     * @param string $output Used to append additional content (passed by reference).
     * @param WP_Post $item Page data object. Not used.
     * @param int $depth Depth of page. Not Used.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     */
    public function end_el( &$output, $item, $depth = 0, $args = array() ) {
        if ( 1 < $depth ) {
            return;
        }

        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }

        $output .= '</' . $this->item->tag . ">{$n}";
    }

    private function html_attributes( array $atts ) {
        $attributes = array();

        foreach ( $atts as $k => $v ) {
            $attributes[] = $k . '="' . esc_attr( $v ) . '"';
        }

        return implode( ' ', $attributes );
    }
}
Sammaye commented 5 years ago

point 1 here is actually false report, I noticed that end() is used to get the last match group, didn't see that

Sammaye commented 5 years ago

Ok, I've basically merged our two classes together today, I stripped out the Schema URLs since they didn't really do much and actually didn't fit with the rules in the XML definition at the URL, so, yeah, anyway, this is what I came up with, it's a bit verbose but I like it like that, makes it insanely easy on the eyes. It is about 80 lines shorter than the class you have, I have also removed the fallback function,. not really needed with modern menu builder, kind of wasted space really.

<?php

/**
 * Class Name: wp_bootstrap_navwalker
 * GitHub URI: https://github.com/twittem/wp-bootstrap-navwalker
 * Description: A custom WordPress nav walker class to implement the Bootstrap 3 navigation style in a custom theme using the WordPress built in menu manager.
 * Version: 2.0.4
 * Author: Edward McIntyre - @twittem
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 */

class Understrap_Walker_Nav_Menu extends Walker_Nav_Menu {

    public $item;

    /**
     * Starts the list before the elements are added.
     *
     * @since 3.0.0
     *
     * @see Walker::start_lvl()
     *
     * @param string $output Used to append additional content (passed by reference).
     * @param int $depth Depth of menu item. Used for padding.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     */
    public function start_lvl( &$output, $depth = 0, $args = array() ) {

        if ( 1 < $depth ) {
            return;
        }

        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }
        $indent = str_repeat( $t, $depth );

        // Default class.
        $classes = array( 'dropdown-menu' );

        /**
         * Filters the CSS class(es) applied to a menu list element.
         *
         * @since 4.8.0
         *
         * @param array $classes The CSS classes that are applied to the menu `<ul>` element.
         * @param stdClass $args An object of `wp_nav_menu()` arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $class_names = implode( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );

        $atts                    = array();
        $atts['class']           = $class_names;
        $atts['aria-labelledby'] = 'navbarDropdown-' . $this->item->ID;

        $attributes = $this->html_attributes( $atts );
        $attributes = $attributes ? ' ' . $attributes : '';

        $output .= "$n$indent<div$attributes>$n";

    }

    public function end_lvl( &$output, $depth = 0, $args = array() ) {

        if ( 1 < $depth ) {
            return;
        }

        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }

        $indent = str_repeat( $t, $depth );
        $output .= "$indent</div>$n";

    }

    /**
     * Starts the element output.
     *
     * @since 3.0.0
     * @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
     *
     * @see Walker::start_el()
     *
     * @param string $output Used to append additional content (passed by reference).
     * @param WP_Post $item Menu item data object.
     * @param int $depth Depth of menu item. Used for padding.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     * @param int $id Current item ID.
     */
    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {

        if ( 1 < $depth ) {
            return;
        }

        $this->item = $item;

        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }
        $indent = $depth ? str_repeat( $t, $depth ) : '';

        /**
         * Filters the arguments for a single nav menu item.
         *
         * @since 4.4.0
         *
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param WP_Post $item Menu item data object.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );

        /**
         * Filters the ID applied to a menu item's list item element.
         *
         * @since 3.0.1
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param string $menu_id The ID that is applied to the menu item's `<li>` element.
         * @param WP_Post $item The current menu item.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth );

        /** This filter is documented in wp-includes/post-template.php */
        $title = apply_filters( 'the_title', $item->title, $item->ID );

        /**
         * Filters a menu item's title.
         *
         * @since 4.4.0
         *
         * @param string $title The menu item's title.
         * @param WP_Post $item The current menu item.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $title = esc_html( apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth ) );

        $classes   = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;

        $state_classes = array();
        $icon_classes  = array();
        foreach ( $classes as $k => $class ) {
            // If any special classes are found, store the class in it's
            // holder array and and unset the item from $classes.
            if ( preg_match( '/^disabled|^sr-only/i', $class ) ) {
                // Test for .disabled or .sr-only classes.
                $state_classes[] = $class;
                unset( $classes[ $k ] );
            } elseif ( preg_match( '/
                ^fa-(\S*)?|^fa(s|r|l|b)-(\S*)?$     # Font Awesome
                |^oi-(\S*)?|^oi(\s?)$               # open-iconic
                |^octicon-(\S*)?|^octicon(\s?)$     # octicons
                |^material-(\S*)?|^material(\s?)$   # Material icons
            /ix', $class ) ) {
                // Font Awesome.
                $icon_classes[] = $class;
                unset( $classes[ $k ] );
            }
        }

        // icons
        $icon = array();
        foreach ( $icon_classes as $k => $class ) {

            $icon_parts = explode( '-', $class );
            if ( 2 < count( $icon_parts ) ) {
                // Skip this since there is something odd here
                continue;
            }

            if ( in_array( $icon_parts[0], [ 'fa', 'fas', 'fab', 'far', 'fal' ] ) ) {
                $icon_parts[1] = 'fa-' . $icon_parts[1];
                $icon[]        = '<span class="' . $icon_parts[0] . ' ' . $icon_parts[1] . '" aria-hidden="true"></span>';
            } else if ( $icon_parts[0] === 'oi' ) {
                $icon_parts[1] = 'oi-' . $icon_parts[1];
                $icon[]        = '<span class="' . $icon_parts[0] . ' ' . $icon_parts[1] . '" aria-hidden="true"></span>';
            } else if ( $icon_parts[0] === 'octicon' ) {
                $icon_parts[1] = 'octicon-' . $icon_parts[1];
                $icon[]        = '<span class="' . $icon_parts[0] . ' ' . $icon_parts[1] . '" aria-hidden="true"></span>';
            } else if ( $icon_parts[0] === 'material' ) {
                $icon_parts[0] = $icon_parts[0] . '-icons';
                $icon[]        = '<span class="' . $icon_parts[0] . '" aria-hidden="true">' . $icon_parts[1] . '</span>';
            }
        }
        $icon = ltrim( implode( '&nbsp;', $icon ), '&nbsp;' );

        foreach ( $classes as $class ) {
            if ( in_array( $class, [
                'current-menu-parent',
                'current-menu-ancestor',
                'current-menu-item',
                'current-page-item'
            ] ) ) {
                $classes[] = 'active';
            }
        }

        $link_atts = array(
            'title'  => ! empty( $item->attr_title ) ? $item->attr_title : $title,
            'target' => ! empty( $item->target ) ? $item->target : '',
            'rel'    => ! empty( $item->xfn ) ? $item->xfn : '',
            'href'   => ! empty( $item->url ) ? $item->url : '',
        );

        if ( in_array( 'sr-only', $state_classes, true ) ) {
            // We don't need the title variable now that it is saved to the <a> attributes above
            $title = $this->sr_wrap( $title );
        }

        if ( 1 === $depth ) {

            if ( in_array( array( 'dropdown-header', 'dropdown-divider', 'dropdown-item-text' ), $classes, true ) ) {

                $this->item->tag = 'div';
                if ( in_array( 'dropdown-item-text', $classes, true ) ) {
                    $this->item->tag = 'span';
                }

                /**
                 * Filters the CSS class(es) applied to a menu item's list item element.
                 *
                 * @since 3.0.0
                 * @since 4.1.0 The `$depth` parameter was added.
                 *
                 * @param array $classes The CSS classes that are applied to the menu item's `<li>` element.
                 * @param WP_Post $item The current menu item.
                 * @param stdClass $args An object of wp_nav_menu() arguments.
                 * @param int $depth Depth of menu item. Used for padding.
                 */
                $classes_string = implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );

                $html_attributes = $this->html_attributes( array(
                    'id'    => $id,
                    'class' => $classes_string
                ) );

                $item_output = "$args->before$indent<{$this->item->tag} $html_attributes>$args->link_before";

                if ( in_array( array( 'dropdown-item-text', 'dropdown-header' ), $classes, true ) ) {
                    $item_output .= ltrim( implode( '&nbsp;', array( $icon, $title ) ), '&nbsp;' );
                }

                $item_output .= $args->link_after . $args->after;

                /**
                 * Filters a menu item's starting output.
                 *
                 * The menu item's starting output only includes `$args->before`, the opening `<a>`,
                 * the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
                 * no filter for modifying the opening and closing `<li>` for a menu item.
                 *
                 * @since 3.0.0
                 *
                 * @param string $item_output The menu item's starting HTML output.
                 * @param WP_Post $item Menu item data object.
                 * @param int $depth Depth of menu item. Used for padding.
                 * @param stdClass $args An object of wp_nav_menu() arguments.
                 */
                $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

                return;

            }

            $this->item->tag = 'a';

            $classes = array_merge( $classes, array( 'dropdown-item' ) );

            if ( in_array( 'disabled', $state_classes, true ) ) {
                $classes[] = 'disabled';

                $link_atts['aria-disabled'] = 'true';
                $link_atts['tabindex']      = '-1';
            }

            /**
             * Filters the CSS class(es) applied to a menu item's list item element.
             *
             * @since 3.0.0
             * @since 4.1.0 The `$depth` parameter was added.
             *
             * @param array $classes The CSS classes that are applied to the menu item's `<li>` element.
             * @param WP_Post $item The current menu item.
             * @param stdClass $args An object of wp_nav_menu() arguments.
             * @param int $depth Depth of menu item. Used for padding.
             */
            $link_atts['class'] = implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );

            $html_attributes = $this->html_attributes( array_filter( $link_atts ) );

            $item_output = "$args->before$indent<{$this->item->tag} $html_attributes>$args->link_before";
            $item_output .= ltrim( implode( '&nbsp;', array( $icon, $title ) ), '&nbsp;' );

            if ( in_array( 'active', $classes ) ) {
                $item_output .= ' ' . $this->sr_wrap( '(current)' );
            }

            $item_output .= $args->link_after . $args->after;

            /**
             * Filters a menu item's starting output.
             *
             * The menu item's starting output only includes `$args->before`, the opening `<a>`,
             * the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
             * no filter for modifying the opening and closing `<li>` for a menu item.
             *
             * @since 3.0.0
             *
             * @param string $item_output The menu item's starting HTML output.
             * @param WP_Post $item Menu item data object.
             * @param int $depth Depth of menu item. Used for padding.
             * @param stdClass $args An object of wp_nav_menu() arguments.
             */
            $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

            return;

        }

        $this->item->tag = 'li';

        $classes[]          = 'nav-item';
        $link_atts['class'] = [ 'nav-link' ];

        if ( in_array( 'disabled', $state_classes ) ) {
            $link_atts['classs'][] = 'disabled';

            $link_atts['aria-disabled'] = 'true';
            $link_atts['tabindex']      = '-1';
        }

        if ( $this->has_children ) {
            $classes[] = 'dropdown';

            $link_atts['id']            = 'navbarDropdown-' . $item->ID;
            $link_atts['role']          = 'button';
            $link_atts['href']          = '#';
            $link_atts['data-toggle']   = 'dropdown';
            $link_atts['class'][]       = 'dropdown-toggle';
            $link_atts['aria-haspopup'] = 'true';
            $link_atts['aria-expanded'] = 'false';
        }
        $link_atts['class'] = implode( ' ', $link_atts['class'] );

        /**
         * Filters the HTML attributes applied to a menu item's anchor element.
         *
         * @since 3.6.0
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param array $link_atts {
         *     The HTML attributes applied to the menu item's `<a>` element, empty strings are ignored.
         *
         * @type string $title Title attribute.
         * @type string $target Target attribute.
         * @type string $rel The rel attribute.
         * @type string $href The href attribute.
         * }
         *
         * @param WP_Post $item The current menu item.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $link_atts = apply_filters( 'nav_menu_link_attributes', array_filter( $link_atts ), $item, $args, $depth );

        /**
         * Filters the CSS class(es) applied to a menu item's list item element.
         *
         * @since 3.0.0
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param array $classes The CSS classes that are applied to the menu item's `<li>` element.
         * @param WP_Post $item The current menu item.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         * @param int $depth Depth of menu item. Used for padding.
         */
        $classes = implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );

        $item_output = $args->before;

        $html_attributes = $this->html_attributes( array(
            'id'    => $id,
            'class' => $classes
        ) );
        $item_output     .= "$indent<li $html_attributes>";

        $link_html_attributes = $this->html_attributes( $link_atts );

        $item_output .= "<a $link_html_attributes>";
        $item_output .= $args->link_before;
        $item_output .= ltrim( implode( '&nbsp;', array( $icon, $title ) ), '&nbsp;' );

        if ( in_array( 'active', $classes ) ) {
            $item_output .= ' ' . $this->sr_wrap( '(current)' );
        }

        $item_output .= "$args->link_after</a>$args->after";

        /**
         * Filters a menu item's starting output.
         *
         * The menu item's starting output only includes `$args->before`, the opening `<a>`,
         * the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
         * no filter for modifying the opening and closing `<li>` for a menu item.
         *
         * @since 3.0.0
         *
         * @param string $item_output The menu item's starting HTML output.
         * @param WP_Post $item Menu item data object.
         * @param int $depth Depth of menu item. Used for padding.
         * @param stdClass $args An object of wp_nav_menu() arguments.
         */
        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }

    /**
     * Ends the element output, if needed.
     *
     * @since 3.0.0
     *
     * @see Walker::end_el()
     *
     * @param string $output Used to append additional content (passed by reference).
     * @param WP_Post $item Page data object. Not used.
     * @param int $depth Depth of page. Not Used.
     * @param stdClass $args An object of wp_nav_menu() arguments.
     */
    public function end_el( &$output, $item, $depth = 0, $args = array() ) {

        if ( 1 < $depth ) {
            return;
        }

        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }

        $output .= '</' . $this->item->tag . ">{$n}";

    }

    private function sr_wrap( $text ) {

        if ( $text ) {
            return '<span class="sr-only">' . $text . '</span>';
        }

        return $text;

    }

    private function html_attributes( array $atts ) {

        $attributes = array();

        foreach ( $atts as $k => $v ) {
            $attributes[] = $k . '="' . ( $k === 'href' ? esc_url( $v ) : esc_attr( $v ) ) . '"';
        }

        return implode( ' ', $attributes );

    }
}
pattonwebz commented 5 years ago

Hey @Sammaye, I am sorry for not responding here for you sooner, I have been busy with work. I'm just leaving a comment here to let you know I have indeed seen this and plan to look through it as soon as I am able.

Sammaye commented 5 years ago

No worries, likewise :)

On Sat, 26 Jan 2019 at 17:09, William Patton notifications@github.com wrote:

Hey @Sammaye https://github.com/Sammaye, I am sorry for not responding here for you sooner, I have been busy with work. I'm just leaving a comment here to let you know I have indeed seen this and plan to look through it as soon as I am able.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/wp-bootstrap/wp-bootstrap-navwalker/issues/408#issuecomment-457847635, or mute the thread https://github.com/notifications/unsubscribe-auth/AATxnIeErHF6DyCAQRXZiFzuOwJDOJX3ks5vHIu9gaJpZM4ZkyFS .

davidshq commented 4 years ago

Curious about the status of this ticket? Have changes been merged or are still outstanding?