Найти тему

Bootstrap NavWalker Class для Wordpress и Bootstrap 5

<?php

/**

* WP Bootstrap Navwalker.

*

* @package WP-Bootstrap-Navwalker

*

* @wordpress-plugin

* Plugin Name: WP Bootstrap Navwalker

* Plugin URI:  https://github.com/wp-bootstrap/wp-bootstrap-navwalker

* Description: A custom WordPress nav walker class to implement the Bootstrap 4 navigation style in a custom theme using the WordPress built in menu manager.

* Author: Edward McIntyre - @twittem, WP Bootstrap, William Patton - @pattonwebz

* Version: 4.3.0

* Author URI: https://github.com/wp-bootstrap

* GitHub Plugin URI: https://github.com/wp-bootstrap/wp-bootstrap-navwalker

* GitHub Branch: master

* License: GPL-3.0+

* License URI: http://www.gnu.org/licenses/gpl-3.0.txt

*/

/* Check if Class Exists. */

if (!class_exists('WP_Bootstrap_Navwalker')) {

/**

* WP_Bootstrap_Navwalker class.

*

* @extends Walker_Nav_Menu

*/

class WP_Bootstrap_Navwalker extends Walker_Nav_Menu

{

/**

* 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 stdClass $args   An object of wp_nav_menu() arguments.

*/

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

{

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 file.

$classes = array('dropdown-menu');

/**

* Filters the CSS class(es) applied to a menu list element.

*

* @since WP 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 = join(' ', apply_filters('nav_menu_submenu_css_class', $classes, $args, $depth));

$class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';

/**

* The `.dropdown-menu` container needs to have a labelledby

* attribute which points to it's trigger link.

*

* Form a string for the labelledby attribute from the the latest

* link with an id that was added to the $output.

*/

$labelledby = '';

// find all links with an id in the output.

preg_match_all('/(<a.*?id=\"|\')(.*?)\"|\'.*?>/im', $output, $matches);

// with pointer at end of array check if we got an ID match.

if (end($matches[2])) {

// build a string to use as aria-labelledby.

$labelledby = 'aria-labelledby="' . esc_attr(end($matches[2])) . '"';

}

$output .= "{$n}{$indent}<ul$class_names $labelledby role=\"menu\">{$n}";

}

/**

* Starts the element output.

*

* @since WP 3.0.0

* @since WP 4.4.0 The {@see 'nav_menu_item_args'} filter was added.

*

* @see Walker_Nav_Menu::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 (isset($args->item_spacing) && 'discard' === $args->item_spacing) {

$t = '';

$n = '';

} else {

$t = "\t";

$n = "\n";

}

$indent = ($depth) ? str_repeat($t, $depth) : '';

$classes = empty($item->classes) ? array() : (array) $item->classes;

// Initialize some holder variables to store specially handled item

// wrappers and icons.

$linkmod_classes = array();

$icon_classes    = array();

/**

* Get an updated $classes array without linkmod or icon classes.

*

* NOTE: linkmod and icon class arrays are passed by reference and

* are maybe modified before being used later in this function.

*/

$classes = self::separate_linkmods_and_icons_from_classes($classes, $linkmod_classes, $icon_classes, $depth);

// Join any icon classes plucked from $classes into a string.

$icon_class_string = join(' ', $icon_classes);

/**

* Filters the arguments for a single nav menu item.

*

*  WP 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);

// Add .dropdown or .active classes where they are needed.

if (isset($args->has_children) && $args->has_children && 0 === $depth) {

$classes[] = 'dropdown';

} elseif (isset($args->has_children) && $args->has_children && 1 === $depth) {

$classes[] = 'dropdown dropend';

} elseif (isset($args->has_children) && $args->has_children && 2 === $depth) {

$classes[] = 'dropdown dropend';

}

if (in_array('current-menu-item', $classes, true) || in_array('current-menu-parent', $classes, true)) {

$classes[] = 'active';

}

// Add some additional default classes to the item.

$classes[] = 'menu-item-' . $item->ID;

$classes[] = 'nav-item';

// Allow filtering the classes.

$classes = apply_filters('nav_menu_css_class', array_filter($classes), $item, $args, $depth);

// Form a string of classes in format: class="class_names".

$class_names = join(' ', $classes);

$class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';

/**

* Filters the ID applied to a menu item's list item element.

*

* @since WP 3.0.1

* @since WP 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);

$id = $id ? ' id="' . esc_attr($id) . '"' : '';

$output .= $indent . '<li itemscope="itemscope" itemtype="https://www.schema.org/SiteNavigationElement"' . $id . $class_names . '>';

// initialize array for holding the $atts for the link item.

$atts = array();

// Set title from item to the $atts array - if title is empty then

// default to item title.

if (empty($item->attr_title)) {

$atts['title'] = !empty($item->title) ? strip_tags($item->title) : '';

} else {

$atts['title'] = $item->attr_title;

}

$atts['target'] = !empty($item->target) ? $item->target : '';

$atts['rel']    = !empty($item->xfn) ? $item->xfn : '';

// If item has_children add atts to <a>.

if (isset($args->has_children) && $args->has_children && 0 === $depth && $args->depth > 1) {

$atts['href']          = '#';

$atts['data-toggle']   = 'dropdown';

$atts['aria-haspopup'] = 'true';

$atts['aria-expanded'] = 'false';

$atts['class']         = 'dropdown-toggle nav-link';

$atts['id']            = 'menu-item-dropdown-' . $item->ID;

} else {

$atts['href'] = !empty($item->url) ? $item->url : '#';

// Items in dropdowns use .dropdown-item instead of .nav-link.

if ($depth > 0) {

$atts['class'] = 'dropdown-item';

} else {

$atts['class'] = 'nav-link';

}

}

if ($args->has_children) {

$atts['data-toggle'] = 'dropdown';

$atts['aria-expanded'] = 'false';

}

if ($args->has_children && $depth > 0) {

$atts['class'] = 'dropdown-item dropdown-toggle';

}

$atts['aria-current'] = $item->current ? 'page' : '';

// update atts of this item based on any custom linkmod classes.

$atts = self::update_atts_for_linkmod_type($atts, $linkmod_classes);

// Allow filtering of the $atts array before using it.

$atts = apply_filters('nav_menu_link_attributes', $atts, $item, $args, $depth);

// Build a string of html containing all the atts for the item.

$attributes = '';

foreach ($atts as $attr => $value) {

if (!empty($value)) {

$value       = ('href' === $attr) ? esc_url($value) : esc_attr($value);

$attributes .= ' ' . $attr . '="' . $value . '"';

}

}

/**

* Set a typeflag to easily test if this is a linkmod or not.

*/

$linkmod_type = self::get_linkmod_type($linkmod_classes);

/**

* START appending the internal item contents to the output.

*/

$item_output = isset($args->before) ? $args->before : '';

/**

* This is the start of the internal nav item. Depending on what

* kind of linkmod we have we may need different wrapper elements.

*/

if ('' !== $linkmod_type) {

// is linkmod, output the required element opener.

$item_output .= self::linkmod_element_open($linkmod_type, $attributes);

} else {

// With no link mod type set this must be a standard <a> tag.

$item_output .= '<a' . $attributes . '>';

}

/**

* Initiate empty icon var, then if we have a string containing any

* icon classes form the icon markup with an <i> element. This is

* output inside of the item before the $title (the link text).

*/

$icon_html = '';

if (!empty($icon_class_string)) {

// append an <i> with the icon classes to what is output before links.

$icon_html = '<i class="' . esc_attr($icon_class_string) . '" aria-hidden="true"></i> ';

}

/** This filter is documented in wp-includes/post-template.php */

$title = apply_filters('the_title', esc_html($item->title), $item->ID);

/**

* Filters a menu item's title.

*

* @since WP 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 the .sr-only class was set apply to the nav items text only.

*/

if (in_array('sr-only', $linkmod_classes, true)) {

$title         = self::wrap_for_screen_reader($title);

$keys_to_unset = array_keys($linkmod_classes, 'sr-only', true);

foreach ($keys_to_unset as $k) {

unset($linkmod_classes[$k]);

}

}

// Put the item contents into $output.

$item_output .= isset($args->link_before) ? $args->link_before . $icon_html . $title . $args->link_after : '';

/**

* This is the end of the internal nav item. We need to close the

* correct element depending on the type of link or link mod.

*/

if ('' !== $linkmod_type) {

// is linkmod, output the required closing element.

$item_output .= self::linkmod_element_close($linkmod_type);

} else {

// With no link mod type set this must be a standard <a> tag.

$item_output .= '</a>';

}

$item_output .= isset($args->after) ? $args->after : '';

/**

* END appending the internal item contents to the output.

*/

$output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args);

}

/**

* Traverse elements to create list from elements.

*

* Display one element if the element doesn't have any children otherwise,

* display the element and its children. Will only traverse up to the max

* depth and no ignore elements under that depth. It is possible to set the

* max depth to include all depths, see walk() method.

*

* This method should not be called directly, use the walk() method instead.

*

* @since WP 2.5.0

*

* @see Walker::start_lvl()

*

* @param object $element           Data object.

* @param array  $children_elements List of elements to continue traversing (passed by reference).

* @param int    $max_depth         Max depth to traverse.

* @param int    $depth             Depth of current element.

* @param array  $args              An array of arguments.

* @param string $output            Used to append additional content (passed by reference).

*/

public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output)

{

if (!$element) {

return;

}

$id_field = $this->db_fields['id'];

// Display this element.

if (is_object($args[0])) {

$args[0]->has_children = !empty($children_elements[$element->$id_field]);

}

parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);

}

/**

* Menu Fallback

* =============

* If this function is assigned to the wp_nav_menu's fallback_cb variable

* and a menu has not been assigned to the theme location in the WordPress

* menu manager the function with display nothing to a non-logged in user,

* and will add a link to the WordPress menu manager if logged in as an admin.

*

* @param array $args passed from the wp_nav_menu function.

*/

public static function fallback($args)

{

if (current_user_can('edit_theme_options')) {

/* Get Arguments. */

$container       = $args['container'];

$container_id    = $args['container_id'];

$container_class = $args['container_class'];

$menu_class      = $args['menu_class'];

$menu_id         = $args['menu_id'];

// initialize var to store fallback html.

$fallback_output = '';

if ($container) {

$fallback_output .= '<' . esc_attr($container);

if ($container_id) {

$fallback_output .= ' id="' . esc_attr($container_id) . '"';

}

if ($container_class) {

$fallback_output .= ' class="' . esc_attr($container_class) . '"';

}

$fallback_output .= '>';

}

$fallback_output .= '<ul';

if ($menu_id) {

$fallback_output .= ' id="' . esc_attr($menu_id) . '"';

}

if ($menu_class) {

$fallback_output .= ' class="' . esc_attr($menu_class) . '"';

}

$fallback_output .= '>';

$fallback_output .= '<li class="nav-item"><a href="' . esc_url(admin_url('nav-menus.php')) . '" class="nav-link" title="' . esc_attr__('Add a menu', 'codeweber') . '">' . esc_html__('Add a menu', 'codeweber') . '</a></li>';

$fallback_output .= '</ul>';

if ($container) {

$fallback_output .= '</' . esc_attr($container) . '>';

}

// if $args has 'echo' key and it's true echo, otherwise return.

if (array_key_exists('echo', $args) && $args['echo']) {

echo $fallback_output; // WPCS: XSS OK.

} else {

return $fallback_output;

}

}

}

/**

* 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) {

// 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) {

// 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]);

}

}

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 $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']);

}

}

}

}

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 = '<span class="sr-only">' . $text . '</span>';

}

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 .= '<span class="dropdown-item-text"' . $attributes . '>';

} 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 .= '<span class="dropdown-header h6"' . $attributes . '>';

} elseif ('dropdown-divider' === $linkmod_type) {

// this is a divider.

$output .= '<div class="dropdown-divider"' . $attributes . '>';

}

return $output;

}

/**

* Return the correct closing tag for the linkmod element.

*

* @since 4.0.0

*

* @param string $linkmod_type a string containing a special linkmod type.

*

* @return string              a string with the closing tag for this linkmod type.

*/

private function linkmod_element_close($linkmod_type)

{

$output = '';

if ('dropdown-header' === $linkmod_type || 'dropdown-item-text' === $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 .= '</span>';

} elseif ('dropdown-divider' === $linkmod_type) {

// this is a divider.

$output .= '</div>';

}

return $output;

}

}

}