WordPress-Intranet: Frontend-Posting mit ACF realisieren

Dies ist ein Beitrag aus der Serie Intranet mit WordPress, in der ich beschreibe, wie ein Intranet-System mit WordPress aufgebaut werden kann, das mehr als ein Blog ist. Hier findet ihr den Ankündigungs-Beitrag. Hier findet ihr die Übersicht aller Beiträge der Serie.


Wie bereits angesprochen, war das umgesetzte Intranet so geplant, dass das WordPress-Backend für normale Benutzer so weit wie möglich vermieden werden sollte. Zwar hat prinzipiell jeder Benutzer Zugriff darauf, er sollte aber für alle üblichen Funktionen nicht darauf zugreifen müssen. Damit Benutzer neue Inhalte einstellen können, kam hier also das Thema Frontend Posting auf.

Frontend Posting (manchmal auch Frontend Post Submission genannt) bedeutet, dass ein Benutzer über die übliche Besucher-Oberfläche (das „Frontend“) Beiträge einreichen bzw. verfassen kann. Oftmals wird hier noch ein Freischaltungs-Prozess zwischengeschaltet, damit nicht beliebige (oder gar illegale) Inhalte auf der Seite veröffentlicht werden können, was aber in einem Intranet nicht unbedingt nötig ist.

Frontend Posting über ACF: das Template

In diesem Projekt stand für mich schon früh fest, dass Advanced Custom Fields zum Einsatz kommt. Hierüber lassen sich die speziellen Metadaten von Inhalten einfach und übersichtlich erstellen, verwalten und abrufen. ACF kommt glücklicherweise bereits mit sehr gutem Support für Frontend Posts, namentlich mit der Funktion acf_form(), die auch gut dokumentiert ist.

Ein einfaches Seiten-Template, über das neue Beiträge erstellt werden können, sieht dann so aus:

<?php
/**
 * Template Name: Neuer Beitrag
 */
?>

<?php acf_form_head(); ?>
<?php get_header(); ?>
<?php the_post(); ?>

<main>
	<article>
		<?php
		acf_form(
			array(
				'post_id' => 'new_post',
				'new_post' => array(
					'post_type' => 'post',
					'post_status' => 'publish'
						),
				'field_groups' => array( 'group_5d4adaf4a04d8' ),
				'return' => '%post_url%&action=create&updated=true',
				'honeypot' => false,
				'submit_value' => 'Neuen Beitrag erstellen',
			)
		);
		?>
	</article>
</main>

<?php get_footer(); ?>

Ich setze hier voraus, dass es bereits eine ACF Feldgruppe mit den benötigten Feldern gibt.

Wichtig und durch ACF bereitgestellt sind die beiden Funktionen acf_form_head() und acf_form(). Über acf_form_head() werden die nötigen Assets (CSS/JS) eingebunden, acf_form() gibt dann das Formular aus. An acf_form() wird ein Options-Array übergeben, das Einstellungen zum Formular enthält.

Der return Parameter wurde in diesem Fall noch genutzt, um einen „Status“ anzuzeigen. Man gelangt nach dem Erstellen eines Beitrages zum Beitrag selbst und erhält (übergeben über die GET-Parameter) einen Hinweis, dass der Beitrag erstellt wurde. In dieser Form funktioniert der Parameter aber nur, wenn die einfache Permalink-Form genutzt wird.

Interessanter als dieses recht normale Template ist die Weiterverarbeitung der ACF-Felder, nachdem ein Beitrag erstellt wurde. Wir erinnern uns: Beiträge im Intranet wurden mit verschiedenen Metadaten ausgestattet. Dazu gehörten unter anderem

  • Ersteller
  • Verantwortlicher
  • Veröffentlichungsdatum
  • Ablaufdatum
  • Bereich (Kategorie)

Konkret wurde über Hooks folgendes gelöst:

  • Ersteller und Verantwortlicher sind standardmäßig der aktuell angemeldete Benutzer
  • Veröffentlichungsdatum ist standardmäßig der jetzige Zeitpunkt
  • Ablaufdatum ist standardmäßig der jetzige Zeitpunkt plus 1 Jahr
  • Kategorie ist standardmäßig der im Benutzer-Profil (des aktuell angemeldeten Benutzers) hinterlegte Wert

Über Hooks wurde während der Erstellung außerdem folgendes geprüft:

  • Ablaufdatum darf nicht vor Veröffentlichungsdatum liegen
  • Ablaufdatum darf nicht mehr als 2 Jahre nach Veröffentlichungsdatum liegen

Nachfolgend möchte ich beispielhaft zeigen, wie man einerseits ACF-Felder eines Frontend-Formulars mit einem Default ausfüllt und andererseits die Eingaben des Benutzers vor dem Speichern prüft.

ACF Frontend-Posting: Felder mit Standard-Wert befüllen

Als Beispiel möchte ich hier zeigen wie man ein Benutzer-Feld (Beitrags-Ersteller) sowie ein Datums-Feld (Veröffentlichungsdatum) mit einem Standard-Wert befüllt.

function cr_acf_post_author_value( $value, $post_id, $field ) {
	if( empty( $value ) ) {
		return get_current_user_id();
	}
	return $value;
}
add_filter( 'acf/load_value/name=ersteller', 'cr_acf_post_author_value', 10, 3 );

Über den Filter acf/load_value/name=xy greifen wir in das Laden des Feld-Wertes ein. Wichtig ist dann direkt der Check if( empty( $value ) ), denn darüber stellen wir sicher, dass wir den Wert nur überschreiben, wenn noch kein Wert gesetzt ist. Dadurch wissen wir, dass das Feld gerade in einer „Neuer Beitrag“-Maske aufgerufen wird. Diese Logik funktioniert natürlich nur, wenn das Feld ein Pflichtfeld ist, und in einem bestehenden Beitrag nicht leer sein kann. Ist ein Feld kein Pflichtfeld, müsste man eine andere Prüfungslogik einsetzen. Innerhalb dieses Checks geben wir einfach get_current_user_id() als Wert zurück, denn darüber lässt sich die ID des aktuell angemeldeten Benutzers abrufen, und genau diese brauchen wir, um das Feld zu befüllen. Easy enough.

function cr_acf_post_time_value( $value, $post_id, $field ) {
	if( empty( $value ) ) {
		$now_date_object = new DateTime( 'now', new DateTimeZone( 'Europe/Berlin' ) );
		$now = $now_date_object->format( 'Y-m-d H:i:00' );
		return $now;
	}
	return $value;
}
add_filter( 'acf/load_value/name=veroeffentlichungsdatum', 'cr_acf_post_time_value', 10, 3 );

Von der Logik her ähnlich sieht es auch aus, wenn wir ein Datums-Feld mit einem Standardwert ausstatten möchten. Wir klinken uns erneut über acf/load_value/name=xy ein und prüfen über einen empty() Check, dass das Feld leer ist. Ist dies der Fall, erstellen wir ein neues DateTime Objekt mit Wert „now“ und der bevorzugten Zeitzone (hier wäre auch die Funktion wp_timezone() möglich, die es seit WordPress 5.3 gibt) und übergeben das passende Format Y-m-d H:i:s. Wir erhalten in Summe ein Datums-Feld, das beim Erstellen eines neuen Beitrags über das Frontend auf den jetzigen Zeitpunkt voreingestellt ist.

Der acf/load_value Filter kann übrigens auch als allgemeiner Filter genutzt werden oder mit /name=x (wie im Beispiel), /type=x oder /key=x verfeinert eingesetzt werden. Die vollständige Dokumentation von ACF ist dabei sehr hilfreich.

Frontend Posting mit ACF: Eingaben prüfen und Daten weiterverarbeiten

Nachdem das Frontend Posting Template steht und die Formular-Felder mit Standard-Werten versehen ist, folgen zwei weitere Schritte: die individuelle Prüfung der eingegebenen Daten auf Gültigkeit und Richtigkeit – also eine Feld-Validierung – sowie das eigentliche Erstellen des neuen Beitrages.

Validierung eines ACF-Feldes

Nachfolgend ein Ausschnitt aus der eingesetzten Feld-Validierung. Dabei wird das Veröffentlichungsdatum und das Ablaufdatum geprüft. Liegt das Ablaufdatum nicht vor dem Veröffentlichungsdatum? Und liegt das Ablaufdatum nicht mehr als 2 Jahre nach dem Veröffentlichungsdatum?

function cr_validate_expiration_date() {
	if( !isset( $_POST['acf']['field_5d5917ca3845c'] ) )
		return false;

	$publish_date_raw = $_POST['acf']['field_5d5917ca3845c'];
	$publish_date_object = DateTime::createFromFormat( 'Y-m-d H:i:s', $publish_date_raw );
	$publish_date = $publish_date_object->getTimestamp();

	$expiration_date_raw = $_POST['acf']['field_5d4bd51ab8ad6'];
	$expiration_date_object = DateTime::createFromFormat( 'Ymd', $expiration_date_raw );
	$expiration_date = $expiration_date_object->getTimestamp();

	// Wenn das Ablaufdatum vor dem Veröffentlichungsdatum liegt
	if( $expiration_date < $publish_date ) {
		acf_add_validation_error( 'acf[field_5d4bd51ab8ad6]', 'Das Ablaufdatum kann nicht vor dem Veröffentlichungsdatum liegen.' );
		return false;
	}

	// Wenn das Ablaufdatum mehr als 730 Tage nach dem Veröffentlichungsdatum liegt
	if( strtotime( '+730 days', $publish_date ) < $expiration_date ) {
		acf_add_validation_error( 'acf[field_5d4bd51ab8ad6]', 'Das Ablaufdatum darf nicht mehr als 730 Tage (2 Jahre) nach dem Veröffentlichungsdatum liegen.' );
		return false;
	}

	return true;
}
add_action( 'acf/validate_save_post', 'cr_validate_expiration_date', 10, 0 );

In Kürze erklärt: Zunächst klinken wir uns in die Action acf/validate_save_post ein. Dies wird durchlaufen, bevor ein Beitrag gespeichert wird. Es können über acf_add_validation_error() eigene Fehlermeldungen zu spezifischen Feldern hinzugefügt werden. Die Werte, die der Benutzer eingegeben hat, holen wir uns aus dem $_POST Objekt, wo die ACF-Daten in einem eigenen Array abgelegt sind. Mit den übergebenen Werten folgt ein einfacher Abgleich von größer/kleiner als, da sich Timestamps hiermit einwandfrei vergleichen lassen. Sind Werte ungültig, wir ein Validation Error für das entsprechende Feld hinzugefügt. Der Benutzer wird dann entsprechend darauf hingewiesen und der Beitrag wird nicht erstellt, bis die Fehler behoben sind.

ACF Frontend Posting: Speichern des neuen Beitrages mit allen Metadaten

Nachdem die Felder geprüft wurden, müssen wir noch dafür sorgen, dass auch ein neuer Beitrag erstellt wird, wenn das Frontend Formular genutzt wird. Hier können wir uns in den acf/pre_save_post Filter einklinken.

function cr_frontend_posting_pre_save_post( $post_id ) {
	if( ! is_numeric( $post_id ) ) {
		return $post_id;
	}

	// Nur eingreifen, wenn das Feld für das Veröffentlichungsdatum in der Anfrage existiert
	if( isset( $_POST['acf']['field_5d5917ca3845c'] ) ) {
		$post_date_object = DateTime::createFromFormat( 'Y-m-d H:i:s', $_POST['acf']['field_5d5917ca3845c'], new DateTimeZone( 'GMT' ) );
		$post_date = $post_date_object->getTimestamp();
	
		$now_date_object = new DateTime( 'now', new DateTimeZone( 'Europe/Berlin' ) );
		$now_date = $now_date_object->getTimestamp();
	
		if( new DateTime( $post_date_object->format( 'Y-m-d H:i:s' ) ) > new DateTime( $now_date_object->format( 'Y-m-d H:i:s' ) ) ) {
			$post_status = 'future';
		} else {
			$post_status = 'publish';
		}
	
		$args = array(
			'ID'				=> $post_id,
			'post_type'			=> 'post',
			'post_title'		=> $_POST['acf']['field_5d595c01c9255'],
			'post_content'		=> $_POST['acf']['field_5d597171102c3'],
			'post_status'		=> $post_status,
			'post_date'			=> $_POST['acf']['field_5d5917ca3845c'],
			'post_author'		=> $_POST['acf']['field_5d4bd5ddb8ad8'],
			'edit_date'			=> true,
			'comment_status'	=> 'open',
		);
	
		$post_id = wp_insert_post( $args );
	}

	return $post_id;
}
add_filter( 'acf/pre_save_post' , 'cr_frontend_posting_pre_save_post', 10, 1 );

Wir greifen nur ein, wenn ein Feld (in diesem Fall das Veröffentlichungsdatum) unseres Frontend-Formulars in der Anfrage existiert. Daraufhin bereiten wir das Datumsobjekt vor. Dieses vergleichen wir noch mit dem aktuellen Zeitpunkt, um Beiträge auch planen zu können, denn im Frontend-Formular kann auch ein zukünftiges Veröffentlichungsdatum gewählt werden – dann muss der post_status „future“ sein. Das args-Array füllen wir mit den übergebenen ACF-Daten (inkl. post_title, post_content und post_author) und legen dann über wp_insert_post den Beitrag an.

Die restlichen Metadaten der ACF-Felder speichert ACF selbst ab, hier müssen wir also nicht separat alle Metadaten prüfen und in die Datenbank speichern.

Recap

Mit diesen Schritten haben wir erfolgreich

  • das Intranet mit einem Frontend Form ausgestattet
  • bestimmte Felder mit einem Standardwert befüllt
  • Felder vor dem Speichern validiert und
  • einen neuen Beitrag erstellt

Natürlich sind dies nur Ausschnitte, aber ich hoffe sie geben einen Einblick in die Logik hinter ACF und Frontend Posting in Zusammenhang mit einem Intranet. Fragen? Fragen. 🙂

Einen Kommentar hinterlassen