Breadcrumb Navigation für WordPress
Seit ein paar Tagen verwende ich hier im Blog eine sogenannte Breadcrumb Navigation, zu Deutsch Brotkrümelnavigation.
Sie soll einem helfen sich auf der Seite zu orientieren und gibt an, wo man sich gerade befindet.
Für WordPress gibt es dafür einemehrere handvoll Plugins.
Da die Plugins aber für mein Vorhaben nicht gereicht haben, habe ich mir eine Breadcrumb Klasse geschrieben, welche ich euch hier auch bereitstellen möchte.
Die Klasse…
- …unterstützt (Custom) Post Types, (Custom) Taxonomies und jede Art von Archiv.
- …kann mit Beiträgen oder eine statischen Seite als Startseite umgehen.
- …kann individuell mit Hilfe von Parametern angepasst werden.
Folgend nun die PHP Klasse:
<?php/*** Breadcrumb Navigation for WordPress.** @author Dominik Schilling* @license GPLv2* @link http://wpgrafie.de/204/** @version 0.1*/class DS_WP_Breadcrumb {/*** The list of breadcrumb items.** @var array* @since 1.0.0*/public $breadcrumb;/*** Templates for link, current/standard state and before/after.** @var array*/public $templates;/*** Various strings.** @var array*/public $strings;/*** Various options.** @var array* @access public*/public $options;/*** Constructor.** @param array $templates An array with templates for link, current/standard state and before/after.* @param array $options An array with options.* @param array $strings An array with strings.* @param bool $autorun Autorun or not.* @return string*/public function __construct( $templates = array(), $options = array(), $strings = array(), $autorun = true ) {$this->templates = wp_parse_args( $templates, array('link' => '<a href="%s">%s</a>','current' => '<span class="c">%s</span>','standard' => '<span class="s">%s</span>','before' => '<nav>','after' => '</nav>') );$this->options = wp_parse_args( $options, array('separator' => ' › ','posts_on_front' => 'posts' == get_option( 'show_on_front' ) ? true : false,'page_for_posts' => get_option( 'page_for_posts' ),'show_pagenum' => true, // support pagination'show_htfpt' => false // show hierarchical terms for post types) );$this->strings = wp_parse_args( $strings, array('home' => 'Startseite','search' => array('s' => 'Suchergebnis zu <em>%s</em>','p' => 'Suchergebnisse zu <em>%s</em>'),'paged' => 'Seite %d','404_error' => 'Fehler: Seite existiert nicht') );// Generate breadcrumbif ( $autorun )echo $this->output();}/*** Return the final breadcrumb.** @return string*/public function output() {if ( empty( $this->breadcrumb ) )$this->generate();$breadcrumb = (string) implode( $this->options['separator'], $this->breadcrumb );return $this->templates['before'] . $breadcrumb . $this->templates['after'];}/*** Build the item based on the type.** @param string|array $item* @param string $type* @return string*/protected function template( $item, $type = 'standard' ) {if ( is_array( $item ) )$type = 'link';switch ( $type ) {case'link':return $this->template(sprintf($this->templates['link'],esc_url( $item['link'] ),$item['title']));break;case 'current':return sprintf( $this->templates['current'], $item );break;case 'standard':return sprintf( $this->templates['standard'], $item );break;}}/*** Helper to generate taxonomy parents.** @param mixed $term_id* @param mixed $taxonomy* @return void*/protected function generate_tax_parents( $term_id, $taxonomy ) {$parent_ids = array_reverse( get_ancestors( $term_id, $taxonomy ) );foreach ( $parent_ids as $parent_id ) {$term = get_term( $parent_id, $taxonomy );$this->breadcrumb["archive_{$taxonomy}_{$parent_id}"] = $this->template( array('link' => get_term_link( $term->slug, $taxonomy ),'title' => $term->name) );}}/*** Generate the breadcrumb.** @return void*/public function generate() {$post_type = get_post_type();$queried_object = get_queried_object();$this->options['show_pagenum'] = ( $this->options['show_pagenum'] && is_paged() ) ? true : false;// Home & Front Page$this->breadcrumb['home'] = $this->template( $this->strings['home'], 'current' );$home_linked = $this->template( array('link' => home_url( '/' ),'title' => $this->strings['home']) );if ( $this->options['posts_on_front'] ) {if ( ! is_home() || $this->options['show_pagenum'] )$this->breadcrumb['home'] = $home_linked;} else {if ( ! is_front_page() )$this->breadcrumb['home'] = $home_linked;if ( is_home() && !$this->options['show_pagenum'] )$this->breadcrumb['blog'] = $this->template( get_the_title( $this->options['page_for_posts'] ), 'current' );if ( ( 'post' == $post_type && ! is_search() && ! is_home() ) || ( 'post' == $post_type && $this->options['show_pagenum'] ) )$this->breadcrumb['blog'] = $this->template( array('link' => get_permalink( $this->options['page_for_posts'] ),'title' => get_the_title( $this->options['page_for_posts'] )) );}// Post Type Archive as indexif ( is_singular() || ( is_archive() && ! is_post_type_archive() ) || is_search() || $this->options['show_pagenum'] ) {if ( $post_type_link = get_post_type_archive_link( $post_type ) ) {$post_type_label = get_post_type_object( $post_type )->labels->name;$this->breadcrumb["archive_{$post_type}"] = $this->template( array('link' => $post_type_link,'title' => $post_type_label) );}}if ( is_singular() ) { // Posts, (Sub)Pages, Attachments and Custom Post Typesif ( ! is_front_page() ) {if ( 0 != $queried_object->post_parent ) { // Get Parents$parents = array_reverse( get_post_ancestors( $queried_object->ID ) );foreach ( $parents as $parent ) {$this->breadcrumb["archive_{$post_type}_{$parent}"] = $this->template( array('link' => get_permalink( $parent ),'title' => get_the_title( $parent )) );}}if ( $this->options['show_htfpt'] ) {$taxonomies = get_object_taxonomies( $post_type, 'objects' );$taxonomies = array_values( wp_list_filter( $taxonomies, array('hierarchical' => true) ) );if ( ! empty( $taxonomies ) ) {$taxonomy = $taxonomies[0]->name; // Get the first taxonomy$terms = get_the_terms( $queried_object->ID, $taxonomy );if ( ! empty( $terms ) ) {$terms = array_values( $terms );$term = $terms[0]; // Get the first termif ( 0 != $term->parent )$this->generate_tax_parents( $term->term_id, $taxonomy );$this->breadcrumb["archive_{$taxonomy}"] = $this->template( array('link' => get_term_link( $term->slug, $taxonomy ),'title' => $term->name) );}}}$this->breadcrumb["single_{$post_type}"] = $this->template( get_the_title(), 'current' );}} elseif ( is_search() ) { // Search$total = $GLOBALS['wp_query']->found_posts;$text = sprintf(_n($this->strings['search']['s'],$this->strings['search']['p'],$total),get_search_query());$this->breadcrumb['search'] = $this->template( $text, 'current' );if ( $this->options['show_pagenum'] )$this->breadcrumb['search'] = $this->template( array('link' => home_url( '?s=' . urlencode( get_search_query( false ) ) ),'title' => $text) );} elseif ( is_archive() ) { // All archive pagesif ( is_category() || is_tag() || is_tax() ) { // Categories, Tags and Custom Taxonomies$taxonomy = $queried_object->taxonomy;if ( 0 != $queried_object->parent && is_taxonomy_hierarchical( $taxonomy ) ) // Get Parents$this->generate_tax_parents( $queried_object->term_id, $taxonomy );$this->breadcrumb["archive_{$taxonomy}"] = $this->template( $queried_object->name, 'current' );if ( $this->options['show_pagenum'] )$this->breadcrumb["archive_{$taxonomy}"] = $this->template( array('link' => get_term_link( $queried_object->slug, $taxonomy ),'title' => $queried_object->name) );} elseif ( is_date() ) { // Date archiveif ( is_year() ) { // Year archive$this->breadcrumb['archive_year'] = $this->template( get_the_date( 'Y' ), 'current' );if ( $this->options['show_pagenum'] )$this->breadcrumb['archive_year'] = $this->template( array('link' => get_year_link( get_query_var( 'year' ) ),'title' => get_the_date( 'Y' )) );} elseif ( is_month() ) { // Month archive$this->breadcrumb['archive_year'] = $this->template( array('link' => get_year_link( get_query_var( 'year' ) ),'title' => get_the_date( 'Y' )) );$this->breadcrumb['archive_month'] = $this->template( get_the_date( 'F' ), 'current' );if ( $this->options['show_pagenum'] )$this->breadcrumb['archive_month'] = $this->template( array('link' => get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) ),'title' => get_the_date( 'F' )) );} elseif ( is_day() ) { // Day archive$this->breadcrumb['archive_year'] = $this->template( array('link' => get_year_link( get_query_var( 'year' ) ),'title' => get_the_date( 'Y' )) );$this->breadcrumb['archive_month'] = $this->template( array('link' => get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) ),'title' => get_the_date( 'F' )) );$this->breadcrumb['archive_day'] = $this->template( get_the_date( 'j' ) );if ( $this->options['show_pagenum'] )$this->breadcrumb['archive_day'] = $this->template( array('link' => get_month_link(get_query_var( 'year' ),get_query_var( 'monthnum' ),get_query_var( 'day' )),'title' => get_the_date( 'F' )) );}} elseif ( is_post_type_archive() && ! is_paged() ) { // Custom Post Type Archive$post_type_label = get_post_type_object( $post_type )->labels->name;$this->breadcrumb["archive_{$post_type}"] = $this->template( $post_type_label, 'current' );} elseif ( is_author() ) { // Author archive$this->breadcrumb['archive_author'] = $this->template( $queried_object->display_name, 'current' );}} elseif ( is_404() ) {$this->breadcrumb['404'] = $this->template( $this->strings['404_error'], 'current' );}if ( $this->options['show_pagenum'] )$this->breadcrumb['paged'] = $this->template(sprintf($this->strings['paged'],get_query_var( 'paged' )),'current');}}?>
Beispiel
Um die Klasse im Theme nutzen zu können, kann man eine kleine Helfer-Funktion schreiben. Diese könnte so aussehen:
<?phpfunction ds_breadcrumb() {require_once( TEMPLATEPATH . '/inc/class-wordpress-breadcrumb.php' );$templates = array('before' => '<nav id="breadcrumb"> Du bist hier » <ul>','after' => '</ul></nav>','standard' => '<li itemscope itemtype="http://data-vocabulary.org/Breadcrumb">%s</li>','current' => '<li class="current">%s</li>','link' => '<a href="%s" itemprop="url"><span itemprop="title">%s</span></a>');$options = array('show_htfpt' => true);$breadcrumb = new DS_WP_Breadcrumb( $templates, $options );}
Jetzt im Theme die Funktion ds_breadcrumb() nur noch aufrufen. Fertig.
Anmerkung
Mit dem HTML5 Beispiel von oben kann Google auch etwas anfangen, siehe hier. Die Suchergebnisse könnten dann so aussehen:

10 Kommentare zu Breadcrumb Navigation für WordPress
Hi Dominik,
es wäre schön, wenn du die Gists auch in de Feed holst, da so der Zusammenhang vollkommen verloren geht und die Bereitstellung des Content daher auch ohne Wert ist.
Ansonsten zu diesem Artikel: schöne Umsetzung.
Danke Frank, das sollte natürlich nicht sein. Kleiner Denkfehler von mir.
Edit: Bug gefixt.
… und dass es sich dabei um eine HTML5 Lösung handelt. Wegen Doctype und so ;)
Das stimmt, hab es ergänzt.
ich habe es soeben in ein Design eingebaut: und wie sagt man neudeutsch: it works like a charme :-) Danke Dominik
Hi Monika,
das freut mich echt! Sollte dir noch ein Fehler auffallen, sag Bescheid. :)
Hi Dominik,
wirklich coole Idee, sollte ich gleich mal ausprobieren
Einfach nur “Danke!” ;-)
Mit dem Aufbau der Klasse war ich etwas unzufrieden, deswegen habe ich die Klasse mal geforkt.
Zu den Änderungen:
- Konstruktor sauber halten
In 3 Wochen weiß kein Anwender mehr in welcher Reihenfolge die Parameter angegeben werden müssen. Bei Klassen bietet es sich daher immer an die Parameter nicht über einen Methoden-Aufruf, sondern über Setter zu setzen.
- Erweiterbarkeit der Klasse
Wenn jemand die Klasse erweitern will, muss er nicht den kompletten Konstruktor neu schreiben, sondern lediglich die Teile, die geändert werden sollen. Soll sich z.B. die Art ändern wie $templates festgelegt wird, muss auch nur diese Methode geändert werden.
- Sprechende Methodennamen
“output” ist nicht wirklich aussagekräftig. Wird eine Variable zurück gegeben? Oder gleich ausgegeben? Oder in der DB gespeichert?
“get_breadcrump” und “print_breadcrump” sagen schon durch ihren Namen was sie machen bzw. leisten. Analog zu sprechenden Variablennamen sollten auch Methoden aussagekräftige Namen haben.
- Getter und Setter haben keine Funktionalität
Was ist wenn sich in Zukunft mal die Arbeitsweise von “output” ändert? Wenn der String also nicht zurück gegeben wird, sondern z.B. in einen Output-Buffer geschrieben wird? Alle die die Klasse verwendet haben müssten ihre Methodenaufrufe, z.B. “echo $breadcrump->output();”, anpassen.
Dem Anwender kann es egal sein wie und woher “get_breadcrump()” und “print_breadcrump()” ihre Daten bekommen. Der Anwender kann sich aber darauf verlassen das diese Methoden immer das gleiche tun: Den Breadcrump zurück geben bzw. ausgeben.
Dadurch ist es möglich in Zukunft die Klasse noch zu verändern ohne das der Anwender seine Scripte anpassen muss.
- Ausgaben filtern
Man sollte dem Anwender die Möglichkeit geben die Ausgabe der Klasse durch einen Filter manipulieren zu können. Häufig ist es einfacher einen Filter zu setzen als die Klasse neu zu instanzieren oder gar abzuleiten.
Das sind Erfahrungen die ich (leidvoll) im Umgang mit Klassen gesammelt habe. Wobei “Trenne Funktonalität von Ausgabe/Rückgabe” im Vordergrund steht. Nichts ist ärgerlicher als seine Scripte nach einem “echo $bc->output();” durchforsten zu müssen weil man an der Klasse etwas geändert hat. Auf den ersten Blick bläht das zwar den Code auf. Es macht aber die Arbeit mit der Klasse in Zukunft um einiges einfacher.
Danke dir fürs forken!
Werde mir deine Punkte für die Zukunft merken und es dementsprechend besser machen. :)
Somit hat der Wechsel zu Github mir schon was gebracht, super.