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( 'sub-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 `
` 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 ) . '"' : '';
if ( isset( $args->has_dropdown ) && $args->has_dropdown ) {
// Get the icon
ob_start();
require get_theme_file_path( 'svg/mobile-nav-arrow-down.svg' );
$icon = ob_get_clean();
$output .= '';
/*
* 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( '/(/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}
{$n}";
} else {
$output .= "\n{$n}{$indent}
{$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_Nav_Menu_Item $item Menu item data object.
* @param int $depth Depth of menu item. Used for padding.
* @param WP_Nav_Menu_Args $args An object of wp_nav_menu() arguments.
* @param int $id Current item ID.
*/
public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
// Indent
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
// Schemas
if ( false !== strpos( $args->items_wrap, 'itemscope' ) && false === $this->has_schema ) {
$this->has_schema = true;
$args->link_before = '' . $args->link_before;
$args->link_after .= '';
}
// Classes
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
// Updating the CSS classes of a menu item in the WordPress Customizer preview results in all classes defined
// in that particular input box to come in as one big class string.
$split_on_spaces = function ( $class ) {
return preg_split( '/\s+/', $class );
};
$classes = $this->flatten( array_map( $split_on_spaces, $classes ) );
/*
* Initialize some holder variables to store specially handled item
* wrappers.
*/
$linkmod_classes = array();
/*
* Get an updated $classes array without linkmod classes.
*
* NOTE: linkmod class arrays are passed by reference and
* are maybe modified before being used later in this function.
*/
$classes = self::separate_linkmods_from_classes( $classes, $linkmod_classes, $depth );
/**
* Filters the arguments for a single nav menu item.
*
* @since WP 4.4.0
*
* @param WP_Nav_Menu_Args $args An object of wp_nav_menu() arguments.
* @param WP_Nav_Menu_Item $item Menu item data object.
* @param int $depth Depth of menu item. Used for padding.
*
* @var WP_Nav_Menu_Args
*/
$args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );
// Add .dropdown or .active classes where they are needed.
if ( $this->has_children ) {
$classes[] = 'dropdown';
}
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[] = 'air-light-menu-item 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 `
` element.
* @param WP_Nav_Menu_Item $item The current menu item.
* @param WP_Nav_Menu_Args $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
$output .= $indent . '
';
// Menu container closing tag.
if ( $show_container ) {
$fallback_output .= '' . $args['container'] . '>';
}
// 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 ) {
$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;
}
/**
* Find any custom linkmod classes and store in their holder
* arrays then remove them from the main classes array.
*
* Supported linkmods: .disabled, .dropdown-header, .dropdown-divider, .screen-reader-text
*
* NOTE: This accepts the linkmod 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 integer $depth an integer holding current depth level.
*
* @return array $classes a maybe modified array of classnames.
*/
private function separate_linkmods_from_classes( $classes, &$linkmod_classes, $depth ) {
// Loop through $classes array to find linkmod 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|^screen-reader-text/i', $class ) ) {
// Test for .disabled or .screen-reader-text 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 ] );
}
}
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 screen-reader-text class.
*/
if ( 'screen-reader-text' !== $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 = '' . $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;
}
/**
* 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 .= '';
} 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;
}
}
return $result;
}
}