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:
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
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:
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.
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!
Ist in der Version 0.1.1 gefixt.
Super Ding. Gefällt mir genau so.
Ich würde das gerne Flattrn.
Ist mittlerweile sogar möglich:
https://dominikschilling.de/danke-sagen/
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?
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.
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.