diff --git a/README.md b/README.md index 92743eb..d5f1ba6 100644 --- a/README.md +++ b/README.md @@ -146,27 +146,20 @@ Bootstrap 5 uses namespaced data attributes. All `data` attributes now include ` ``` -The walker also adds a data attribute for dropdown toggles via the `start_el()` method. Paste this to your functions.php to make the walker use the infixed data attibute. +To enable Bootstrap 5 support add a `bs_version` key to the array of arguments passed to the `wp_nav_menu()` function. -```php -add_filter( 'nav_menu_link_attributes', 'prefix_bs5_dropdown_data_attribute', 20, 3 ); -/** - * Use namespaced data attribute for Bootstrap's dropdown toggles. - * - * @param array $atts HTML attributes applied to the item's `` element. - * @param WP_Post $item The current menu item. - * @param stdClass $args An object of wp_nav_menu() arguments. - * @return array - */ -function prefix_bs5_dropdown_data_attribute( $atts, $item, $args ) { - if ( is_a( $args->walker, 'WP_Bootstrap_Navwalker' ) ) { - if ( array_key_exists( 'data-toggle', $atts ) ) { - unset( $atts['data-toggle'] ); - $atts['data-bs-toggle'] = 'dropdown'; - } - } - return $atts; -} +```diff +wp_nav_menu( array( + 'theme_location' => 'primary', + 'depth' => 2, // 1 = no dropdowns, 2 = with dropdowns. + 'container' => 'div', + 'container_class' => 'collapse navbar-collapse', + 'container_id' => 'bs-example-navbar-collapse-1', + 'menu_class' => 'navbar-nav mr-auto', + 'fallback_cb' => 'WP_Bootstrap_Navwalker::fallback', + 'walker' => new WP_Bootstrap_Navwalker(), ++ 'bs_version' => 5, +) ); ``` ### Menu Caching diff --git a/class-wp-bootstrap-navwalker.php b/class-wp-bootstrap-navwalker.php index 9b0d424..06604e1 100644 --- a/class-wp-bootstrap-navwalker.php +++ b/class-wp-bootstrap-navwalker.php @@ -18,591 +18,649 @@ */ // Check if Class Exists. -if ( ! class_exists( 'WP_Bootstrap_Navwalker' ) ) : +if ( class_exists( 'WP_Bootstrap_Navwalker' ) ) { + return; +} + +/** + * WP_Bootstrap_Navwalker class. + */ +class WP_Bootstrap_Navwalker extends Walker_Nav_Menu { + + /** + * Whether the items_wrap contains schema microdata or not. + * + * @since 4.2.0 + * @var boolean + */ + private $has_schema = false; + + /** + * The default major Bootstrap version for which to create the markup. + * + * @var int + */ + private $bs_version = 4; + + /** + * Ensures the items_wrap argument contains microdata. + * + * @since 4.2.0 + */ + public function __construct() { + if ( ! has_filter( 'wp_nav_menu_args', array( $this, 'add_schema_to_navbar_ul' ) ) ) { + add_filter( 'wp_nav_menu_args', array( $this, 'add_schema_to_navbar_ul' ) ); + } + } + /** - * WP_Bootstrap_Navwalker class. + * Starts the list before the elements are added. + * + * @since WP 3.0.0 + * + * @see Walker_Nav_Menu::start_lvl() + * + * @param string $output Used to append additional content (passed by reference). + * @param int $depth Depth of menu item. Used for padding. + * @param WP_Nav_Menu_Args $args An object of `wp_nav_menu()` arguments. */ - class WP_Bootstrap_Navwalker extends Walker_Nav_Menu { + public function start_lvl( &$output, $depth = 0, $args = null ) { + if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) { + $t = ''; + $n = ''; + } else { + $t = "\t"; + $n = "\n"; + } + $indent = str_repeat( $t, $depth ); + + // Default class to add to the start of the list (usually ul tag). + $classes = array( 'dropdown-menu' ); /** - * Whether the items_wrap contains schema microdata or not. + * Filters the CSS class(es) applied to a menu list element. + * + * @since WP 4.8.0 * - * @since 4.2.0 - * @var boolean + * @param array $classes The CSS classes that are applied to the menu `'; - // Menu container closing tag. - if ( $show_container ) { - $fallback_output .= ''; - } + // Menu container closing tag. + if ( $show_container ) { + $fallback_output .= ''; + } - // if $args has 'echo' key and it's true echo, otherwise return. - if ( array_key_exists( 'echo', $args ) && $args['echo'] ) { - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo $fallback_output; - } else { - return $fallback_output; - } + // If $args has 'echo' key and it's true echo, otherwise return. + if ( array_key_exists( 'echo', $args ) && $args['echo'] ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $fallback_output; + } else { + return $fallback_output; } + } - /** - * Filter to ensure the items_Wrap argument contains microdata. - * - * @since 4.2.0 - * - * @param array $args The nav instance arguments. - * @return array $args The altered nav instance arguments. - */ - public function add_schema_to_navbar_ul( $args ) { - if ( isset( $args['items_wrap'] ) ) { - $wrap = $args['items_wrap']; - if ( strpos( $wrap, 'SiteNavigationElement' ) === false ) { - $args['items_wrap'] = preg_replace( '/(>).*>?\%3\$s/', ' itemscope itemtype="http://www.schema.org/SiteNavigationElement"$0', $wrap ); - } + /** + * Filter to ensure the items_wrap argument contains microdata. + * + * @since 4.2.0 + * + * @param array $args The nav instance arguments. + * @return array $args The altered nav instance arguments. + */ + public function add_schema_to_navbar_ul( $args ) { + if ( isset( $args['items_wrap'] ) ) { + $wrap = $args['items_wrap']; + if ( strpos( $wrap, 'SiteNavigationElement' ) === false ) { + $args['items_wrap'] = preg_replace( '/(>).*>?\%3\$s/', ' itemscope itemtype="http://www.schema.org/SiteNavigationElement"$0', $wrap ); } - return $args; } + return $args; + } - /** - * Find any custom linkmod or icon classes and store in their holder - * arrays then remove them from the main classes array. - * - * Supported linkmods: .disabled, .dropdown-header, .dropdown-divider, .sr-only - * Supported iconsets: Font Awesome 4/5, Glypicons - * - * NOTE: This accepts the linkmod and icon arrays by reference. - * - * @since 4.0.0 - * - * @param array $classes an array of classes currently assigned to the item. - * @param array $linkmod_classes an array to hold linkmod classes. - * @param array $icon_classes an array to hold icon classes. - * @param integer $depth an integer holding current depth level. - * - * @return array $classes a maybe modified array of classnames. - */ - private function separate_linkmods_and_icons_from_classes( $classes, &$linkmod_classes, &$icon_classes, $depth ) { - // Loop through $classes array to find linkmod or icon classes. - foreach ( $classes as $key => $class ) { + /** + * Finds any custom linkmod or icon classes and store in their holder + * arrays then remove them from the main classes array. + * + * Supported linkmods: .disabled, .dropdown-header, .dropdown-divider, .sr-only + * Supported iconsets: Font Awesome 4/5, Glypicons + * + * NOTE: This accepts the linkmod and icon arrays by reference. + * + * @since 4.0.0 + * + * @param array $classes An array of classes currently assigned to the item. + * @param array $linkmod_classes An array to hold linkmod classes. + * @param array $icon_classes An array to hold icon classes. + * @param integer $depth An integer holding current depth level. + * @return array $classes A maybe modified array of classnames. + */ + private function separate_linkmods_and_icons_from_classes( $classes, &$linkmod_classes, &$icon_classes, $depth ) { + // Loop through $classes array to find linkmod or icon classes. + foreach ( $classes as $key => $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. + $linkmod_classes[] = $class; + unset( $classes[ $key ] ); + } elseif ( preg_match( '/^dropdown-header|^dropdown-divider|^dropdown-item-text/i', $class ) && $depth > 0 ) { /* - * If any special classes are found, store the class in it's - * holder array and and unset the item from $classes. + * Test for .dropdown-header or .dropdown-divider and a + * depth greater than 0 - IE inside a dropdown. */ - if ( preg_match( '/^disabled|^sr-only/i', $class ) ) { - // Test for .disabled or .sr-only classes. - $linkmod_classes[] = $class; - unset( $classes[ $key ] ); - } elseif ( preg_match( '/^dropdown-header|^dropdown-divider|^dropdown-item-text/i', $class ) && $depth > 0 ) { - /* - * Test for .dropdown-header or .dropdown-divider and a - * depth greater than 0 - IE inside a dropdown. - */ - $linkmod_classes[] = $class; - unset( $classes[ $key ] ); - } elseif ( preg_match( '/^fa-(\S*)?|^fa(s|r|l|b)?(\s?)?$/i', $class ) ) { - // Font Awesome. - $icon_classes[] = $class; - unset( $classes[ $key ] ); - } elseif ( preg_match( '/^glyphicon-(\S*)?|^glyphicon(\s?)$/i', $class ) ) { - // Glyphicons. - $icon_classes[] = $class; - unset( $classes[ $key ] ); - } + $linkmod_classes[] = $class; + unset( $classes[ $key ] ); + } elseif ( preg_match( '/^fa-(\S*)?|^fa(s|r|l|b)?(\s?)?$/i', $class ) ) { + // Font Awesome. + $icon_classes[] = $class; + unset( $classes[ $key ] ); + } elseif ( preg_match( '/^glyphicon-(\S*)?|^glyphicon(\s?)$/i', $class ) ) { + // Glyphicons. + $icon_classes[] = $class; + unset( $classes[ $key ] ); } - - return $classes; } - /** - * Return a string containing a linkmod type and update $atts array - * accordingly depending on the decided. - * - * @since 4.0.0 - * - * @param array $linkmod_classes array of any link modifier classes. - * - * @return string empty for default, a linkmod type string otherwise. - */ - private function get_linkmod_type( $linkmod_classes = array() ) { - $linkmod_type = ''; - // Loop through array of linkmod classes to handle their $atts. - if ( ! empty( $linkmod_classes ) ) { - foreach ( $linkmod_classes as $link_class ) { - if ( ! empty( $link_class ) ) { - - // Check for special class types and set a flag for them. - if ( 'dropdown-header' === $link_class ) { - $linkmod_type = 'dropdown-header'; - } elseif ( 'dropdown-divider' === $link_class ) { - $linkmod_type = 'dropdown-divider'; - } elseif ( 'dropdown-item-text' === $link_class ) { - $linkmod_type = 'dropdown-item-text'; - } + return $classes; + } + + /** + * Returns a string containing a linkmod type and updates the $atts + * array accordingly depending on the decided. + * + * @since 4.0.0 + * + * @param array $linkmod_classes Array of any link modifier classes. + * @return string Empty for default, a linkmod type string otherwise. + */ + private function get_linkmod_type( $linkmod_classes = array() ) { + $linkmod_type = ''; + // Loop through array of linkmod classes to handle their $atts. + if ( ! empty( $linkmod_classes ) ) { + foreach ( $linkmod_classes as $link_class ) { + if ( ! empty( $link_class ) ) { + + // Check for special class types and set a flag for them. + if ( 'dropdown-header' === $link_class ) { + $linkmod_type = 'dropdown-header'; + } elseif ( 'dropdown-divider' === $link_class ) { + $linkmod_type = 'dropdown-divider'; + } elseif ( 'dropdown-item-text' === $link_class ) { + $linkmod_type = 'dropdown-item-text'; } } } - return $linkmod_type; } + return $linkmod_type; + } - /** - * Update the attributes of a nav item depending on the limkmod classes. - * - * @since 4.0.0 - * - * @param array $atts array of atts for the current link in nav item. - * @param array $linkmod_classes an array of classes that modify link or nav item behaviors or displays. - * - * @return array maybe updated array of attributes for item. - */ - private function update_atts_for_linkmod_type( $atts = array(), $linkmod_classes = array() ) { - if ( ! empty( $linkmod_classes ) ) { - foreach ( $linkmod_classes as $link_class ) { - if ( ! empty( $link_class ) ) { - /* - * Update $atts with a space and the extra classname - * so long as it's not a sr-only class. - */ - if ( 'sr-only' !== $link_class ) { - $atts['class'] .= ' ' . esc_attr( $link_class ); - } - // Check for special class types we need additional handling for. - if ( 'disabled' === $link_class ) { - // Convert link to '#' and unset open targets. - $atts['href'] = '#'; - unset( $atts['target'] ); - } elseif ( 'dropdown-header' === $link_class || 'dropdown-divider' === $link_class || 'dropdown-item-text' === $link_class ) { - // Store a type flag and unset href and target. - unset( $atts['href'] ); - unset( $atts['target'] ); - } + /** + * Updates the attributes of a nav item depending on the limkmod classes. + * + * @since 4.0.0 + * + * @param array $atts Array of atts for the current link in nav item. + * @param array $linkmod_classes An array of classes that modify link or nav item behaviors or displays. + * @return array Maybe updated array of attributes for item. + */ + private function update_atts_for_linkmod_type( $atts = array(), $linkmod_classes = array() ) { + if ( ! empty( $linkmod_classes ) ) { + foreach ( $linkmod_classes as $link_class ) { + if ( ! empty( $link_class ) ) { + /* + * Update $atts with a space and the extra classname + * so long as it's not a sr-only class. + */ + if ( 'sr-only' !== $link_class ) { + $atts['class'] .= ' ' . esc_attr( $link_class ); + } + // Check for special class types we need additional handling for. + if ( 'disabled' === $link_class ) { + // Convert link to '#' and unset open targets. + $atts['href'] = '#'; + unset( $atts['target'] ); + } elseif ( 'dropdown-header' === $link_class || 'dropdown-divider' === $link_class || 'dropdown-item-text' === $link_class ) { + // Store a type flag and unset href and target. + unset( $atts['href'] ); + unset( $atts['target'] ); } } } - return $atts; } + return $atts; + } - /** - * Wraps the passed text in a screen reader only class. - * - * @since 4.0.0 - * - * @param string $text the string of text to be wrapped in a screen reader class. - * @return string the string wrapped in a span with the class. - */ - private function wrap_for_screen_reader( $text = '' ) { - if ( $text ) { - $text = '' . $text . ''; - } - return $text; + /** + * Wraps the passed text in a screen reader only class. + * + * @since 4.0.0 + * + * @param string $text The string of text to be wrapped in a screen reader class. + * @return string The string wrapped in a span with the class. + */ + private function wrap_for_screen_reader( $text = '' ) { + if ( $text ) { + $text = '' . $text . ''; } + return $text; + } - /** - * Returns the correct opening element and attributes for a linkmod. - * - * @since 4.0.0 - * - * @param string $linkmod_type a sting containing a linkmod type flag. - * @param string $attributes a string of attributes to add to the element. - * - * @return string a string with the openign tag for the element with attribibutes added. - */ - private function linkmod_element_open( $linkmod_type, $attributes = '' ) { - $output = ''; - if ( 'dropdown-item-text' === $linkmod_type ) { - $output .= ''; - } elseif ( 'dropdown-header' === $linkmod_type ) { - /* - * For a header use a span with the .h6 class instead of a real - * header tag so that it doesn't confuse screen readers. - */ - $output .= ''; - } elseif ( 'dropdown-divider' === $linkmod_type ) { - // This is a divider. - $output .= ''; } + return $output; + } - /** - * Flattens a multidimensional array to a simple array. - * - * @param array $array a multidimensional array. - * - * @return array a simple array - */ - public function flatten( $array ) { - $result = array(); - foreach ( $array as $element ) { - if ( is_array( $element ) ) { - array_push( $result, ...$this->flatten( $element ) ); - } else { - $result[] = $element; - } + /** + * Flattens a multidimensional array to a simple array. + * + * @param array $array A multidimensional array. + * @return array A simple array. + */ + public function flatten( $array ) { + $result = array(); + foreach ( $array as $element ) { + if ( is_array( $element ) ) { + array_push( $result, ...$this->flatten( $element ) ); + } else { + $result[] = $element; } - return $result; } - + return $result; } -endif; + /** + * Retrieves the Bootstrap version to use. + * + * @param WP_Nav_Menu_Args $args An object of `wp_nav_menu()` arguments. + * @return int Major Bootstrap version + */ + private function get_bs_version( $args ) { + if ( property_exists( $args, 'bs_version' ) && 5 === (int) $args->bs_version ) { + return (int) $args->bs_version; + } + return $this->bs_version; + } +}