Skip to content

Instantly share code, notes, and snippets.

@mcaskill
Created November 28, 2014 06:59
Show Gist options
  • Save mcaskill/d22334bfc954bc91a12b to your computer and use it in GitHub Desktop.
Save mcaskill/d22334bfc954bc91a12b to your computer and use it in GitHub Desktop.
WordPress : Displays a location breadcrumb trail using WordPress navigation menus.
<?php
/**
* Displays a location breadcrumb trail.
*
* Uses WordPress nav menu items and theme locations to build
* a breadcrumb trail. Overall, this template functon replicates
* most of the procedures found in `wp_nav_menu()`.
*
* @param array $args {
* Optional. Array of nav menu arguments.
*
* @type string $menu Desired menu. Accepts (matching in order) id, slug, name. Default empty.
* @type string $theme_location Theme location to be used. Must be registered with register_nav_menu()
* in order to be selectable by the user.
* @type string $container Whether to wrap the ul, and what to wrap it with. Default 'nav'.
* @type string $container_class Class that is applied to the container. Default 'menu-{menu slug}-container'.
* @type string $container_id The ID that is applied to the container. Default empty.
* @type callback|bool $fallback_cb If the menu doesn't exists, a callback function will fire.
* Default is 'wp_page_menu'. Set to false for no fallback.
* @type string $title_home Anchor text for the front page.
* @type bool $include_home Add front page crumb.
* @type bool $include_single Add single post crumb.
* @type string $item_before Text before the link text. Default empty.
* @type string $item_after Text after the link text. Default empty.
* @type string $item_wrap How the item should be rendered.
* Uses printf() format with numbered placeholders.
* @type string $item_separator How the list items should be separated. Default is an arrow.
* @type string $link_before Text before the label. Default empty.
* @type string $link_after Text after the label. Default empty.
* @type string $link_wrap How the link should be rendered. Default is an anchor with an id, class, and href attribute.
* Uses printf() format with numbered placeholders.
* @type string $current_wrap How the current label should be rendered.
* Uses printf() format with numbered placeholders.
* Same numbered placeholders as $link_wrap.
* @type bool $echo Whether to echo the menu or return it. Default true.
* }
* @return mixed Menu output if $echo is false, false if there are no items or no menu was found.
*/
function wp_nav_trail( $args = [] )
{
$defaults = [
'echo' => true,
'menu' => '',
'theme_location' => '',
'container' => 'nav',
'container_class' => '',
'container_id' => '',
'fallback_cb' => '',
'title_home' => __('Home'),
'include_home' => true,
'include_single' => false,
'item_before' => '',
'item_after' => '',
'item_wrap' => '<span id="%1$s" class="%2$s" typeof="v:Breadcrumb">%3$s</span>',
'item_separator' => ' &#8594; ',
'link_before' => '',
'link_after' => '',
'link_wrap' => '<a href="%1$s" rel="v:url" property="v:title">%2$s</a>',
'current_wrap' => '<span property="v:title">%2$s</span>'
];
$args = wp_parse_args( $args, $defaults );
/**
* Filter the arguments used to display a breadcrumb trail.
*
* @see {function} WordPress\Nav_Menus\wp_nav_menu()
* @see {filter} wp_nav_menu_args
*
* @param array $args Array of wp_nav_trail() arguments.
*/
$args = apply_filters( 'wp_nav_trail_args', $args );
$args = (object) $args;
/**
* Filter whether to short-circuit the wp_nav_trail() output.
*
* Returning a non-null value to the filter will short-circuit
* wp_nav_trail(), echoing that value if $args->echo is true,
* returning that value otherwise.
*
* @see {function} WordPress\Nav_Menus\wp_nav_menu()
* @see {filter} pre_wp_nav_menu
*
* @param string|null $output Nav trail output to short-circuit with. Default null.
* @param object $args An object containing wp_nav_trail() arguments.
*/
$nav_menu = apply_filters( 'pre_wp_nav_trail', null, $args );
if ( null !== $nav_menu ) {
if ( $args->echo ) {
echo $nav_menu;
return;
}
return $nav_menu;
}
// Get the nav menu based on the requested menu
$menu = wp_get_nav_menu_object( $args->menu );
// Get the nav menu based on the theme_location
if ( ! $menu && $args->theme_location && ( $locations = get_nav_menu_locations() ) && isset( $locations[ $args->theme_location ] ) ) {
$menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
}
// get the first menu that has items if we still can't find a menu
if ( ! $menu && ! $args->theme_location ) {
$menus = wp_get_nav_menus([ 'orderby' => 'name' ]);
foreach ( $menus as $menu_maybe ) {
if ( $menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, [ 'update_post_term_cache' => false ] ) ) {
$menu = $menu_maybe;
break;
}
}
}
// If the menu exists, get its items.
if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) )
$menu_items = wp_get_nav_menu_items( $menu->term_id, [ 'update_post_term_cache' => false ] );
/*
* If no menu was found:
* - Fall back (if one was specified), or bail.
*
* If no menu items were found:
* - Fall back, but only if no theme location was specified.
* - Otherwise, bail.
*/
if ( ( ! $menu || is_wp_error( $menu ) || ( isset( $menu_items ) && empty( $menu_items ) && ! $args->theme_location ) )
&& $args->fallback_cb && is_callable( $args->fallback_cb ) ) {
return call_user_func( $args->fallback_cb, (array) $args );
}
if ( ! $menu || is_wp_error( $menu ) ) {
return false;
}
$nav_menu = $items = '';
$show_container = false;
if ( $args->container ) {
/**
* Filter the list of HTML tags that are valid for use as breadcrumb containers.
*
* @see {function} WordPress\Nav_Menus\wp_nav_menu()
* @see {filter} wp_nav_menu_container_allowedtags
*
* @param array $tags The acceptable HTML tags for use as breadcrumb containers.
* Default is array containing 'div' and 'nav'.
*/
$allowed_tags = apply_filters( 'wp_nav_trail_container_allowedtags', [ 'div', 'nav' ] );
if ( in_array( $args->container, $allowed_tags ) ) {
$show_container = true;
$atts = [];
$atts['id'] = ( $args->container_id ? esc_attr( $args->container_id ) : '' );
$atts['class'] = ( $args->container_class ? esc_attr( $args->container_class ) : 'trail-' . $menu->slug . '-container' );
$atts['prefix'] = 'v:http://rdf.data-vocabulary.org/#';
/**
* Filter the HTML attributes applied to a trail's container.
*
* @see {method} WordPress\Nav_Menus\Walker_Nav_Menu::start_el()
* @see {filter} nav_menu_link_attributes
*
* @param array $atts {
* The HTML attributes applied to the trail's container, empty strings are ignored.
*
* @type string $id The ID attribute.
* @type string $class The class names attribute.
* @type string $prefix The prefix attribute.
* }
* @param array $args An array of wp_nav_trail() arguments.
*/
$atts = apply_filters( 'nav_trail_container_attributes', $atts, $args );
$attributes = '';
foreach ( $atts as $attr => $value ) {
if ( ! empty( $value ) ) {
$attributes .= ' ' . $attr . '="' . esc_attr( $value ) . '"';
}
}
$nav_menu .= '<'. $args->container . $attributes . '>';
}
}
// Set up the $menu_item variables
_wp_menu_item_classes_by_context( $menu_items );
$sorted_menu_items = [];
foreach ( (array) $menu_items as $menu_item ) {
$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
}
unset( $menu_items, $menu_item );
/**
* Filter the sorted list of breadcrumb objects before generating the trail's HTML.
*
* @see {function} WordPress\Nav_Menus\wp_nav_menu()
* @see {filter} wp_nav_menu_objects
*
* @param array $sorted_menu_items The breadcrumnb items, sorted by each breadcrumb's menu order.
* @param object $args An object containing wp_nav_trail() arguments.
*/
$sorted_menu_items = apply_filters( 'wp_nav_trail_objects', $sorted_menu_items, $args );
$items = [];
if ( $args->include_home ) {
$show_on_front = get_option('show_on_front');
$page_on_front = get_option('page_on_front');
// $page_for_posts = get_option('page_for_posts');
if ( ( $show_on_front === 'page' && is_front_page() ) || ( $show_on_front === 'posts' && is_home() ) ) {
// Do nothing
}
else {
$front_page = (object) [
'ID' => $page_on_front,
'db_id' => $page_on_front,
'object_id' => 0,
'object' => 'page',
'post_parent' => 0,
'post_name' => 'home',
'menu_item_parent' => 0,
'type' => 'post_type',
'title' => $args->title_home,
'url' => home_url(),
'description' => '',
'attr_title' => '',
'classes' => [],
'target' => '',
'xfn' => '',
'current' => is_front_page(),
'current_item_parent' => false,
'current_item_ancestor' => true
];
array_unshift( $sorted_menu_items, $front_page );
}
}
/** @todo $args->include_single */
if ( $args->include_single ) {
}
foreach ( $sorted_menu_items as $item ) {
if ( $item->current_item_ancestor || $item->current_item_parent || $item->current ) {
$classes = ( empty( $item->classes ) ? [] : (array) $item->classes );
/**
* Filter the CSS class(es) applied to a breadcrumb's <a>.
*
* @see {method} WordPress\Nav_Menus\Walker_Nav_Menu::start_el()
* @see {filter} nav_menu_css_class
*
* @param array $classes The CSS classes that are applied to the breadcrumb's <a>.
* @param object $item The current breadcrumb.
* @param array $args An array of wp_nav_trail() arguments.
*/
$class_names = implode( ' ', apply_filters( 'nav_trail_css_class', array_filter( $classes ), $item, $args ) );
/**
* Filter the ID applied to a breadcrumb's <a>.
*
* @see {method} WordPress\Nav_Menus\Walker_Nav_Menu::start_el()
* @see {filter} nav_menu_item_id
*
* @param string $item_id The ID that is applied to the breadcrumb's <a>.
* @param object $item The current breadcrumb.
* @param array $args An array of wp_nav_trail() arguments.
*/
$id = apply_filters( 'nav_trail_item_id', 'crumb-item-' . $item->ID, $item, $args );
/**
* Filter the URL applied to a breadcrumb's <a>.
*
* @param string $item_id The URL that is applied to the breadcrumb's <a>.
* @param object $item The current breadcrumb.
* @param array $args An array of wp_nav_trail() arguments.
*/
$url = apply_filters( 'nav_trail_item_url', $item->url, $item, $args );
$title = $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$format = ( $item->current ? $args->current_wrap : $args->link_wrap );
/**
* Filter a breadcrumb's output format.
*
* @param string $format The breadcrumb's HTML output.
* @param object $item Trail item data object.
* @param object $args An object containing wp_nav_trail() arguments.
*/
$format = apply_filters( 'nav_trail_item_format', $format, $item, $args );
$link_output = $args->item_before;
$link_output .= sprintf( $format, esc_url( $url ), $title );
$link_output .= $args->item_after;
$item_output = sprintf( $args->item_wrap, esc_attr( $id ), esc_attr( $class_names ), $link_output );
/**
* Filter a breadcrumb's output.
*
* @see {method} WordPress\Nav_Menus\Walker_Nav_Menu::start_el()
* @see {filter} walker_nav_menu_start_el
*
* @param string $item_output The breadcrumb's HTML output.
* @param object $item Trail item data object.
* @param object $args An object containing wp_nav_trail() arguments.
*/
$items[] = apply_filters( 'nav_trail_item', $item_output, $items, $args );
}
}
if ( function_exists('first') ) {
/**
* Filter the first item (most likely the root ancestor).
*
* @param string $item_output The breadcrumb's HTML output.
* @param object $item Trail item data object.
* @param array $args An array of wp_nav_trail() arguments.
*/
apply_filters( 'nav_trail_start', first( $items ), reset( $sorted_menu_items ), $args );
}
if ( function_exists('last') ) {
/**
* Filter the last item (most likely the active one).
*
* @param string $item_output The breadcrumb's HTML output.
* @param object $item Trail item data object.
* @param array $args An array of wp_nav_trail() arguments.
*/
apply_filters( 'nav_trail_end', last( $items ), end( $sorted_menu_items ), $args );
}
/**
* Filter the separator applied between each item.
*
* @param string $separator The URL that is applied to the breadcrumb's <a>.
* @param array $items The trail's HTML output.
* @param array $args An array of wp_nav_trail() arguments.
*/
$separator = apply_filters( 'nav_trail_separator', $args->item_separator, $items, $args );
$items = implode( $separator, $items );
unset( $sorted_menu_items );
/**
* Filter the HTML list content for navigation menus.
*
* @see {function} WordPress\Nav_Menus\wp_nav_menu()
* @see {filter} wp_nav_menu_items
*
* @param string $items The HTML list content for the breadcrumbs.
* @param object $args An object containing wp_nav_trail() arguments.
*/
$items = apply_filters( 'wp_nav_trail_items', $items, $args );
/**
* Filter the HTML list content for a specific navigation menu.
*
* @see {function} WordPress\Nav_Menus\wp_nav_menu()
* @see {filter} wp_nav_menu_{$menu->slug}_items
*
* @param string $items The HTML list content for the breadcrumbs.
* @param object $args An object containing wp_nav_trail() arguments.
*/
$items = apply_filters( "wp_nav_trail_{$menu->slug}_items", $items, $args );
// Don't print any markup if there are no items at this point.
if ( empty( $items ) ) {
return false;
}
$nav_menu .= $items;
unset( $items );
if ( $show_container ) {
$nav_menu .= '</' . $args->container . '>';
}
/**
* Filter the HTML content for navigation breadcrumb trail.
*
* @see {function} WordPress\Nav_Menus\wp_nav_menu()
* @see {filter} wp_nav_menu
*
* @param string $nav_menu The HTML content for the navigation breadcrumb trail.
* @param object $args An object containing wp_nav_trail() arguments.
*/
$nav_menu = apply_filters( 'wp_nav_trail', $nav_menu, $args );
if ( $args->echo ) {
echo $nav_menu;
}
else {
return $nav_menu;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment