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 eine mehrere 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 / RAW / github:gist
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
<?php
/**
 * Klasse zum Erstellen einer WordPress Breadcrumb Navigation.
 *
 * @author Dominik Schilling
 * @license GPLv2
 * @link https://dominikschilling.de/204/
 *
 * @version 0.1.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(
				'singular' => 'Ein Suchergebnis zu <em>%s</em>',
				'plural'   => '%s Suchergebnisse zu <em>%s</em>'
			),
			'paged' => 'Seite %d',
			'404_error' => 'Fehler: Seite existiert nicht'
		) );

		// Generate breadcrumb
		if ( $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 index
		if ( 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 Types
			if ( ! is_front_page() ) {
				if ( $this->options['show_htfpt'] ) {
					$_id = $queried_object->ID;
					$_post_type = $post_type;

					if ( is_attachment() ) {
						// Show terms of the parent page
						$_id = $queried_object->post_parent;
						$_post_type = get_post_type( $_id );
					}

					$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( $_id, $taxonomy );

						if ( ! empty( $terms ) ) {
							$terms = array_values( $terms );
							$term = $terms[0]; // Get the first term

							if ( 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
							) );
						}
					}
				}

				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 )
						) );
					}
				}


				$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']['singular'],
					$this->strings['search']['plural'],
					$total
				),
				$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 pages
			if ( 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 archive
				if ( 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:

PHP / RAW / github:gist
<?php
function 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:

WordPress Breadcrumb in der Google Suche

18 thoughts on “Breadcrumb Navigation für WordPress”

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

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

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

  3. Frage, weil ich grad selbst keine Lösung finde:
    Bei Anhang-Seiten (Attachment) wird in den breadcrumbs leider keine Kategorie mehr angezeigt, sondern als Parent nur noch der Hauptbeitrag. Kann man bestimmt ändern, nur wie? ;-)
    Danke für die Hilfe!

  4. Danke für diese Hilfestellung, kann ich die 1:1 so übernehmen und die funktioniert? Oder kann ich dem Tool noch mit auf den Weg geben, was er tun soll (welche Kategorie, wenn ein Artikel unter mehreren Sachen gelistet ist?)

    Was mich auch interessiert, wie ist denn das mit der sogenannten Vorschau auf den Text (140 oder 160 Zeichen) wird die dadurch kürzer?

    1. Hi,

      ja, du kannst sie so übernehmen, siehe das Beispiel für die genaue Implementierung.
      Die Klasse ist so konzipiert, dass sie immer die erste Kategorie/Tag nimmt und anzeigt. “Mit auf den Weg geben” geht hier nicht, dafür müsstest du die Klasse ändern, siehe Zeile 207 ff.

      Was du mit der Vorschau meinst, ist mir nicht ganz klar.

  5. Hey Dominik,
    Super-Tool und funktioniert bestens.
    Trotzdem die Frage: Gibt es eine Möglichkeit, sich Ober- und Unterpunkte anzeigen zu lassen, so im Stile: Startseite > Bildergalerien > Bildergalerie A

    Bekomme das, dank mangelnder PHP-Programmierkentnisse, einfach nicht hin. Leider.
    Danke schon mal im Vorraus.

Leave a Reply