WordPress Page Template für einen Google+ Feed

Google+ ist im Kommen, auch gerade wegen den neuen Unternehmensseiten, welche unter anderem als Blog genutzt werden können. Doch einen RSS Feed sucht man vergeblich.

Unternehmensseite: Google+ Your Business

Für mein Projekt WordPress Trac Activity wolte ich für die nicht Google+ Nutzer einen Feed anbieten. Google selbst bietet aber keinen Feed für den Google+ Stream an.

Aus diesem Grund habe ich ein Page Template für WordPress entwickelt, welches die Möglichkeit bietet, einen Feed für einen Google+ Stream zu erstellen.

Das Page Template

Folgend der etwas größere Schnipsel mit anschließender Erklärung.

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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
<?php
/**
 * Template Name: Google+ Feed
 */


/**
 * A WordPress page template for a Google+ feed.
 *
 * @author Dominik Schilling
 * @license GPLv2
 * @link https://dominikschilling.de/567/
 *
 * @version 0.2
 */
class Google_Plus_Feed {
	/**
	 * Google+ API Key.
	 * See https://code.google.com/apis/console/
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	private $api_key = '';

	/**
	 * Google+ User ID.
	 *
	 * @since 0.1
	 *
	 * @var string
	 */
	private $user_id = '';
	
	/**
	 * Custom settings.
	 * See $this->set_settings()
	 *
	 * @since 0.1
	 *
	 * @var array
	 */
	private $settings = array();
	
	/**
	 * Don't change the lines after these
	 * if you don't know what you do.
	 */

	/**
	 * Google+ activities.
	 *
	 * @since 0.1
	 *
	 * @var mixed
	 */
	private $activities;

	/**
	 * Current queried object.
	 *
	 * @since 0.1
	 *
	 * @var object
	 */
	private $curr_obj;

	/**
	 * Construct.
	 *
	 * @since 0.1
	 *
	 * @return void
	 */
	function __construct() {
		if( empty( $this->curr_obj ) )
			$this->curr_obj = get_queried_object();

		if( ! $this->get_api_key() || ! $this->get_user_id() )
			wp_die( 'Not configured!' );

		$this->set_settings();

		$this->activities = $this->build_activities();

		if ( empty( $this->activities ) )
			wp_die( 'Please load again.' );

		$this->feed_output();
	}

	/**
	 * Get the API key from
	 *    post custom field (gplusfeed_api_key)
	 * or
	 *    $this->api_key
	 *
	 * @since 0.1
	 *
	 * @return string|bool Key on success, false if key is missing.
	 */
	function get_api_key() {
		$api_key = get_post_meta( $this->curr_obj->ID, 'gplusfeed_api_key', true );

		if ( ! empty( $api_key ) )
			return $api_key;
		else if( ! empty( $this->api_key ) )
			return $this->api_key;
		else
			return false;
	}

	/**
	 * Get the user id from
	 *    post custom field (gplus_feed_user_id)
	 * or
	 *    $this->user_id
	 *
	 * @since 0.1
	 *
	 * @return string|bool Key on success, false if key is missing.
	 */
	function get_user_id() {
		$user_id = get_post_meta( $this->curr_obj->ID, 'gplus_feed_user_id', true );

		if ( ! empty( $user_id ) )
			return $user_id;
		else if( ! empty( $this->user_id ) )
			return $this->user_id;
		else
			return false;
	}

	/**
	 * Set custom feed settings.
	 *
	 *  - limit: How many activities should be displayed in feed
	 *  - cache_time: How long the feed should be cached
	 *  - language: The language of the feed
	 *  - update_period: See http://web.resource.org/rss/1.0/modules/syndication/
	 *  - update_frequency: See http://web.resource.org/rss/1.0/modules/syndication/
	 *
	 * @since 0.1
	 *
	 * @return array Settings saved in an array.
	 */
	function set_settings() {
		$this->settings = wp_parse_args(
			array(
				'limit' => 10,
				'cache_time' => 300,
				'language' => 'de',
				'update_period' => 'hourly',
				'update_frequency' => 1
			),
			$this->settings
		);
	}

	/**
	 * Get the Google+ API link.
	 * See https://developers.google.com/+/api/latest/activities/list
	 *
	 * @since 0.1
	 *
	 * @return string URL of the Google+ API.
	 */
	function get_api_link() {
		$api_link = add_query_arg(
			array(
				'alt' => 'json',
				'maxResults' => $this->settings['limit'],
				'pp' => 1,
				'key' => $this->get_api_key()
			),
			sprintf( 'https://www.googleapis.com/plus/v1/people/%s/activities/public', $this->get_user_id() )
		);

		return esc_url_raw( $api_link );
	}

	/**
	 * Build the activity stream and save it in a transient.
	 *
	 * @since 0.1
	 *
	 * @return string|bool Activities in XML format or false on error.
	 */
	function build_activities() {
		$dev = true;
		if ( false !== ( $activities = get_transient( $this->get_cache_key() ) ) && ! $dev )
			return $activities;

		$activities = $this->get_activities();

		if ( is_wp_error( $activities ) )
			return false;

		$activities = $this->render_activities( $activities );

		if ( empty( $activities ) )
			return false;

		// Cache activities
		set_transient( $this->get_cache_key(), $activities, $this->settings['cache_time'] );

		return $activities;
	}

	/**
	 * Build an individual cache key.
	 * Based on settings, user id and API key.
	 *
	 * @since 0.1
	 *
	 * @return string The cache key.
	 */
	function get_cache_key() {
		return 'gplus_feed_' . md5(
			implode( '|', $this->settings )
			. $this->get_user_id()
			. $this->get_api_key()
		);
	}

	/**
	 * Get the activities from Google+ API.
	 *
	 * @since 0.1
	 *
	 * @return array|WP_Error API result in an array or WP_Error on API/HTTP error.
	 */
	function get_activities() {
		$result = wp_remote_retrieve_body(
			wp_remote_get(
				$this->get_api_link()
			)
		);

		if ( ! empty( $result ) ) {
			$result = (array) json_decode( $result );

			if ( empty ( $results->error ) )
				return $result;
			else
				return new WP_Error( 'api_error', 'API ERROR', $results->error );
		} else {
			return new WP_Error( 'http_error', 'HTTP ERROR');
		}
	}

	/**
	 * Render the activities and return them in XML format.
	 *
	 * @since 0.1
	 *
	 * @param array $activities
	 * @return array Rendered activity items for feed.
	 */
	function render_activities( $activities ) {
		$feed = array();

		foreach( $activities['items'] as $item => $data ) {
			if ( ! preg_match('/^<bb[^>]*>(.*?)</b>/i', $data->object->content, $matches ) ) {
				$title = $data->title;
				$content = $data->object->content;
			} else {
				$title = strip_tags( $matches[1] );
				$content = str_replace( $matches[0] . '<br /><br />', '', $data->object->content );
			}

			if ( ! empty( $data->object->attachments ) )
				$content .= $this->_render_activity_attachments( $data->object->attachments );

			$feed[$item] = "
<item>
	<title>{$title}</title>
	<link>{$data->url}</link>
	<pubDate>" . mysql2date( 'D, d M Y H:i:s +0000', $data->published, false ) . "</pubDate>
	<dc:creator>{$data->actor->displayName}</dc:creator>
	<guid isPermaLink="false">{$data->url}</guid>

	<description><![CDATA[" . wp_trim_words( $content, 20 ) . "]]></description>
	<content:encoded><![CDATA[{$content}]]></content:encoded>

	<slash:comments>{$data->object->replies->totalItems}</slash:comments>
</item>
";
		}

		return $feed;
	}

	/**
	 * Handle activity attachments, like video, photo and links (articles).
	 *
	 * @since 0.2
	 *
	 * @param array $attachments
	 * @return string Attachments in HTML format.
	 */
	function _render_activity_attachments( $attachments ) {
		$articles = $photos = $videos = array();

		foreach ( $attachments as $attachment => $meta ) {
			switch ( $meta->objectType ) {
				case 'article' :
					$host = @parse_url( $meta->url, PHP_URL_HOST );
					$articles[] = sprintf(
						'%s<a href="%s" title="%s">%s</a>',
						empty( $host ) ? '' : $host . ' – ',
						esc_url( $meta->url ),
						empty( $meta->content ) ? '' : esc_attr( $meta->content ),
						empty( $meta->displayName ) ? '' : $meta->displayName
					);
					break;
				case 'photo' :
					$photos[] = sprintf(
						'%s<img src="%s" height="%s" width="%s" alt="%s" title="%s" style="margin:5px;max-width:250px;height:auto" />%s',
						empty( $meta->fullImage->url ) ? '' : '<a href="' . esc_url( $meta->fullImage->url ) . '">',
						esc_url( $meta->image->url ),
						empty( $meta->image->height ) ? '' : esc_attr( $meta->image->height ),
						empty( $meta->image->width ) ? '' : esc_attr( $meta->image->width ),
						empty( $meta->displayName ) ? '' : $meta->displayName,
						empty( $meta->displayName ) ? '' : $meta->displayName,
						empty( $meta->fullImage->url ) ? '' : '</a>'
					);
					break;
				case 'video' :
					$videos[] = sprintf(
						'<a href="%s"><img src="%s" height="%s" width="%s" alt="%s" title="%s" style="margin:5px;max-width:250px;height:auto" /></a>',
						esc_url( $meta->url ),
						esc_url( $meta->image->url ),
						empty( $meta->image->height ) ? '' : esc_attr( $meta->image->height ),
						empty( $meta->image->width ) ? '' : esc_attr( $meta->image->width ),
						empty( $meta->displayName ) ? '' : $meta->displayName,
						empty( $meta->displayName ) ? '' : $meta->displayName
					);
					break;
			}
		}
		
		$content = '';
		$_html = '<div style="display:table-row"><div style="display:table-cell;vertical-align:top;padding:0 10px 0 0"><b>%s:</b></div>';
		if ( ! empty( $articles ) ) {
			$articles = implode( '<br />', $articles );
			$content .= sprintf(
				$_html,
				__( 'Links' )
			);
			$content .= sprintf(
				'<div style="display:table-cell">%s</div></div>',
				$articles
			);
		}
		
		if ( ! empty( $photos ) ) {
			$photos = implode( '', $photos );
			$content .= sprintf(
				$_html,
				__( 'Images' )
			);
			$content .= sprintf(
				'<div style="display:table-cell">%s</div></div>',
				$photos
			);
		}
		
		if ( ! empty( $videos ) ) {
			$videos = implode( '', $videos );
			$content .= sprintf(
				$_html,
				__( 'Video' )
			);
			$content .= sprintf(
				'<div style="display:table-cell">%s</div></div>',
				$videos
			);
		}
		
		if ( ! empty( $content ) )
			$content = sprintf(
				'<br /><br /><h4>%s</h4><div style="display:table">%s</div>',
				__( 'Images and Attachments' ),
				$content
			);

		return $content;
	}

	/**
	 * Build the feed output.
	 *
	 * @since 0.1
	 *
	 * @return void
	 */
	function feed_output() {
		$this->get_rss_header();
		$this->get_rss_content();
		$this->get_rss_footer();
	}

	/**
	 * Retrieve information about the feed.
	 *
	 * @since 0.1
	 *
	 * @param string $show Feed info to retrieve.
	 * @param bool $echo Return or print the info. Default: true.
	 * @return string Feed info.
	 */
	function get_feed_info( $show, $echo = true ) {
		switch ( $show ) {
			case 'title' :
				$output = apply_filters( 'the_title_rss', $this->curr_obj->post_title ) ;
				break;
			case 'self' :
				$host = @parse_url( home_url() );
				$host = $host['host'];
				$output = esc_url(
					( is_ssl() ? 'https' : 'http' ) . '://'
					. $host
					. stripslashes( $_SERVER['REQUEST_URI'] )
				);
				break;
			case 'link' :
				$output = esc_url( sprintf( 'https://plus.google.com/%d/', $this->get_user_id() ) );
				break;
			case 'desc' :
				$output = apply_filters( 'the_title_rss', $this->curr_obj->post_content );
				break;
			case 'time' :
				$timeout = get_option( '_transient_timeout_' . $this->get_cache_key() );
				if ( $timeout === false )
					$time = time();
				else
					$time = $timeout - $this->settings['cache_time'];
				$output = date( 'D, d M Y H:i:s +0000', $time );
				break;
			case 'language' :
				$output = $this->settings['language'];
				break;
			case 'update_period' :
				$output = $this->settings['update_period'];
				break;
			case 'update_frequency' :
				$output = $this->settings['update_frequency'];
				break;
		}

		if( ! $echo )
			return $output;

		echo $output;
	}

	/**
	 * Print the feed header.
	 *
	 * @since 0.1
	 *
	 * @return void
	 */
	function get_rss_header() {
		header( 'Content-Type: text/xml; charset=UTF-8', true );
		echo '<?xml version="1.0" encoding="UTF-8"?'.'>';
	?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
	<title><?php $this->get_feed_info( 'title' ); ?></title>
	<atom:link href="<?php $this->get_feed_info( 'self' ); ?>" rel="self" type="application/rss+xml" />
	<link><?php $this->get_feed_info( 'link' ); ?></link>
	<description><?php $this->get_feed_info( 'desc' ); ?></description>
	<lastBuildDate><?php $this->get_feed_info( 'time' ); ?></lastBuildDate>
	<language><?php $this->get_feed_info( 'language' ); ?></language>
	<sy:updatePeriod><?php $this->get_feed_info( 'update_period' ); ?></sy:updatePeriod>
	<sy:updateFrequency><?php $this->get_feed_info( 'update_frequency' ); ?></sy:updateFrequency>
	<?php
	}

	/**
	 * Print the feed content/activities.
	 *
	 * @since 0.1
	 *
	 * @return void
	 */
	function get_rss_content() {
		foreach( $this->activities as $item )
			echo $item;
	}

	/**
	 * Print the feed footer.
	 *
	 * @since 0.1
	 *
	 * @return void
	 */
	function get_rss_footer() {
	?>
</channel>
</rss>
	<?php
	}
}

// Init
$feed = new Google_Plus_Feed();

Das obige Page Template sollte mit jedem Theme kompatibel sein und muss ins das Themeverzeichnis des aktuellen Themes kopiert werden.
Bei Twenty Eleven wäre das zum Beispiel /wp-content/themes/twentyeleven/google-plus-feed.php.

Für die Einrichtung des Feeds werden ein Google+ API Key sowie die Google+ User ID benötigt:

Google+ API Key

Wie jede API von Google benötigt auch die Google+ API einen API Key, welcher vorher generiert werden muss.

Google+ API freischalten

Generiert kann sich dieser in der Google APIs Console unter code.google.com/apis/console/. Unter dem Tab Services muss dazu der zugehörige Regler auf On gesetzt werden.

Google+ User ID

Die User ID des Google+ Profils kann aus der jeweiligen URL zum Profil herausgefiltert werden, ein Beispiel:

https://plus.google.com/u/0/101675293278434581718/posts

101675293278434581718 wären in diesem Fall die User ID.

Einrichtung

Wenn API Key und User ID vorhanden sind, kann der Feed eingerichtet werden.
Dazu ins Backend wechseln und eine neue Seite einrichten, Titel und Inhalt (= Beschreibung des Feeds, ohne HTML Tags) festlegen und in der Metabox Attribute das Template Google+ Feed definieren.
Anschließend müssen der Key und die ID übergeben werden, dazu gibt es zwei Möglichkeiten:

  1. Die heruntergeladene Datei google-plus-feed.php öffnen und die Variablen private $user_id = ''; und private $api_key = '' mit den jeweiligen Werten belegen.
  2. Die zweite Möglichkeit geht über die benutzerdefinierten Feldern (ggf. über Reiter Optionen einblenden aktivieren). Dafür müssen zwei neue Felder angelegt werden, einmal gplus_feed_user_id mit dem Wert der User ID und einmal gplusfeed_api_key mit dem Wert des API Keys.

Speichern nicht vergessen und die Grundeinrichtung ist getan. Der Feed sollte jetzt erreichbar sein.

Zusätzliche Einstellungen

Weitere Anpassung an den RSS Feed können durch die Variable $this->settings getätigt werden. Dazu sollte sich die Methode set_settings() zu Rate gezogen werden.

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
<?php

<?php
/**
 * Set custom feed settings.
 *
 *  - limit: How many activities should be displayed in feed
 *  - cache_time: How long the feed should be cached
 *  - language: The language of the feed
 *  - update_period: See http://web.resource.org/rss/1.0/modules/syndication/
 *  - update_frequency: See http://web.resource.org/rss/1.0/modules/syndication/
 *
 * @since 0.1
 *
 * @return array Settings saved in an array.
 */
function set_settings() {
	$this->settings = wp_parse_args(
		array(
			'limit' => 10,
			'cache_time' => 300,
			'language' => 'de',
			'update_period' => 'hourly',
			'update_frequency' => 1
		),
		$this->settings
	);
}

Formatierung

Die Google+ API gibt als Titel nur die ersten paar Wörter aus.
Dies kann aber umgangen werden, wenn der Google+ Post mit einer in Fett formatierten Zeile gefolgt von zwei Absätzen beginnt, ein Bespiel:

Google+ Formatierung für die Feeddarstellung

Feedback

Feedback, Verbesserungsvorschläge, Probleme oder sonstige Fragen können gerne als Kommentar abgegeben werden und werden dort natürlich von mir beantwortet.

WordPress Filter debuggen

WordPress hat viele Filter, wo sich Entwickler einklinken können. Durch einen Filter ist es möglich, bestimmte Daten vor der Ausgabe zu filtern.

Manchmal ist es dann interessant zu wissen, welche Funktion welchen Ausgabe filtert.Dafür habe ich mir folgende Funktion geschrieben, welche jeder gerne benutzen und weiter entwickeln darf.

PHP / RAW / github:gist
<?php
/**
 * Debug WordPress filters.
 *
 * Use add_action( 'shutdown', 'ds_debug_filters' ) to display all used
 * filters with the functions hooked into the filter.
 *
 * @author Dominik Schilling
 * @license GPLv2
 * @link https://dominikschilling.de/262/
 *
 * @version 0.1.1
 *
 * Changelog:
 * Version 0.1.1 - Fixed string for more then 1 accepted arguments.
 *
 */
function ds_debug_filters( $custom_tags = array() ) {
	// $wp_filter Stores all of the filters
	global $wp_filter;

	if ( empty( $wp_filter ) )
		return false;

	// Check if custom tags are defined
	if ( ! empty( $custom_tags ) ) {
		// Check if custom tags are available
		$tags = array_intersect( array_keys( $wp_filter), (array) $custom_tags );

		if ( empty( $tags ) )
			return false;

		// Fill custom tags
		foreach ( $tags as $tag )
			$_wp_filter[$tag] = $wp_filter[$tag];
	} else {
		// Use default tags
		$_wp_filter = $wp_filter;
	}

	echo '<pre id="wp-debug-filters">';

	// Uncomment, if you want to sort by name of the filter hooks
	// ksort( $_wp_filter );

	foreach ( $_wp_filter as $tag => $data ) {
		// Print tag name
		printf(
			'<br /><strong>%s</strong><br />',
			esc_html( $tag )
		);

		// Sort by priority
		ksort( $data );

		foreach ( $data as $priority => $functions ) {
			// Print priority once
			printf(
				'%s',
				$priority
			);

			// Go through each function
			foreach ( $functions as $function ) {
				$_function = $function['function'];
				$_args = $function['accepted_args'];

				// Check function type
				if ( is_array( $_function ) ) {
					// Object class calling
					if ( is_object( $_function[0] ) )
						$class = get_class( $_function[0] );
					else
						$class = $_function[0];

					$name = $class . '::' . $_function[1];
				} else {
					// Static calling
					$name = $_function;
				}

				// Print function name and number of accepted arguments
				printf(
					"t%s() (%s)<br />",
					esc_html( $name ),
					sprintf(
						_n(
							'1 accepted argument',
							'%s accepted arguments',
							$_args
						),
						$_args
					)
				);
			}
		}
	}

	echo '</pre>';
}
Mögliche Ausgabe der Funktion

Ein Dank geht an Andrey Savchenko für die Idee.

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:

Google +1 Button via jQuery und Ajax asynchron laden

JavaScript / RAW / github:gist
( function( $ ) {
	$( document ).ready( function() {
		// Skript wird nur geladen, wenn ein Artikel in der Einzelansicht angezeigt wird.
		if ( $( 'body.single' ).length ) {
			$.ajax( {
				type: 'GET',
				url: 'https://apis.google.com/js/plusone.js', // Link zur plusone.js Datei.
				timeout: 1000, // Passiert innerhalb von 1000ms nichts, wird der Ladevorgang abgebrochen.
				dataType: 'script',
				cache: true, // Javascript Datei cachen lassen.
				success:
					function() { // Bei Erfolg Variable lang auf de setzen.
						window.___gcfg = { lang: 'de' };
					},
				error:
					function() { // Bei einem Fehler kann z.B. etwas entfernt werden.
						$( '.google' ).parent().hide();
					}
			} );
		};
	} );
} )( jQuery );

Hinweis

Dieser Codeschnipsel eignet sich natürlich nur, wenn jQuery schon anderweitig genutzt wird.
Weitere Informationen gibt es auf der offiziellen Seite des +1 Button Generators.

post_class() um Klasse für Elternkategorie erweitern

Die Funktion post_class() kann standardmäßig folgende Klassen ausgeben:

  • .post-id
  • .post
  • .attachment
  • .sticky
  • .hentry (hAtom microformat pages)
  • .category-ID
  • .category-name
  • .tag-name
  • .format-name

Leider wird aber keine Klasse für den Namen der Elternkategorie ausgegeben, folgende Funktion für die functions.php löst dieses Problem.

PHP / RAW / github:gist
<?php
/**
 * Erweitert post_class() um eine Klasse für Elternkategorien.
 *
 * @author Dominik Schilling
 * @license GPLv2
 * @link https://dominikschilling.de/178/
 *
 * @version 0.2
 */
function ds_add_parent_category_class( $classes ) {
    $cats = get_the_category();

    if ( empty( $cats ) )
        return $classes;

    foreach ( $cats as $cat ) {
        $parent_cat = get_category( $cat->category_parent );
        if ( ! is_wp_error( $parent_cat ) )
            $classes[] = 'parent-category-' . sanitize_html_class( $parent_cat->slug, $parent_cat->term_id );
    }

    return $classes;
}

add_filter( 'post_class', 'ds_add_parent_category_class' );

Beispiel

Einem Artikel ist die Kategorie Plugins zugeordnet. Plugins ist eine Unterkategorie von WordPress.
Die Ausgabe von post_class() könnte dann folgendermaßen aussehen:

<div class="post-151 post type-post status-publish format-standard hentry category-plugins parent-category-wordpress">