Im Rahmen eines Kundenprojekts gilt es, Posts in WordPress quasi von außen zu importieren. Genauer gesagt muss ich den kompletten Spielplan einer Fußballmannschaft für eine Saison einlesen. Der Spielplan liegt mir als Excel-Datei vor.
Das „Problem“ bei der Sache ist: Mein Spielplan ist in WordPress über Custom Post Types, Custom Fields und Taxonomien abgelegt. Alles Dinge, die in einem „Abwasch“ bei der Verarbeitung berücksichtigt werden müssen.
Nun ist es ja kein Hexenwerk, mittels PHP und mySQL entsprechende Datensätze in den jeweiligen WordPress-Tabellen anzulegen. Genauso hatte ich es von Anfang an gemacht; immer rein damit ins WordPress. Ich wunderte mich nur, dass ich in WordPress selbst danach noch einiges an Handgriffen zu tun hatte, damit die Posts auch funktionieren. Heute nun reichte es mir und ich setzte mich hin, um eine elegante – auch vom Kunden bedienbare – Lösung zu finden. Vielleicht werde ich das Kundenprojekt ja nicht ewig supporten. Wer weiß das heute schon so genau. Dann sollte es dem Kunden aber möglich sein, selbst die entsprechenden Handgriffe zu tun. (Eigentlich wäre jetzt genau der Punkt, um mal mein letztes (und erstes) Kundenprojekt vorzustellen, aber … das mache ich später.) Habe ich alle Klammern zu … ja? … also weiter.
Es folgt eine Step by Step Anleitung …
Datei hochladen und Abarbeiten
Als erstes musste ich dafür sorgen, dass der Benutzer eine CSV-Datei hochladen kann. Im Netz finden sich zig Anleitungen zu dem Thema. Ich entschied mich für eine kleine, schlanke Lösung in einer einzigen PHP-Datei.
Zunächst einmal das HTML-Formular für das „Benutzerinterface“:
<h1>Spielplan-Upoad</h1> <h2>Die CSV-Datei muss wie folgt aufgebaut sein!</h2> <table> <tr><td>1. Spalte</td><td>Spieldatum im Format TT.MM.JJ</td></tr> <tr><td>2. Spalte</td><td>Spielzeit im Format HH:MM</td></tr> <tr><td>3. Spalte</td><td>Heimmannschaft</td></tr> <tr><td>4. Spalte</td><td>Auswärtsmannschaft</td></tr> </table> <form action="#" method="post" enctype="multipart/form-data"> <p> <select name="saison" size="1"> <option value="2014-2015">2014-2015</option> </select> </p> <p> <select name="auswahl" size="1"> <option value="1. Mannschaft">1. FC Unschlagbar</option> <option value="2. Mannschaft">2. Mannschaft</option> </select> </p> <p>Bitte die Datei auswaehlen !!<br> <input name="datei" type="file" size="50" maxlength="100000"> </p> <input type="submit" name="submit" value="Hochladen"> <input type="reset" value="Abbrechen"> </form>
Als CSV-Datei erwarte ich eine Datei, in der jeder Datensatz eine Zeile ist, die Spalten nicht mit Anführungszeichen versehen und mit Semikolon getrennt sind. Also eine stinknormale CSV-Datei.
Meine Kundenseite beinhaltet mehrere Mannschaften und Saisons, so dass ich vor dem Upload gleich abfrage, für welche Mannschaft und welche Saison der Spielplan ist.
Über das Formular ruft sich die Seite quasi selbst wieder auf, so dass in der PHP-Datei über diesem HTML-Bereich der folgende PHP-Code für den Upload und die Verarbeitung der Daten verantwortlich ist:
<?php if($_POST['submit'] == "Hochladen") { $saison = $_POST["saison"]; $auswahl = $_POST["auswahl"]; $name = "./temp/".$_FILES['datei']['name']; move_uploaded_file($_FILES['datei']['tmp_name'], "$name"); if(file_exists($name)) { $fp = fopen($name, "r"); define("CSV_DATUM", 0); define("CSV_ZEIT", 1); define("CSV_HOME", 2); define("CSV_AWAY", 3); while( !feof($fp) ) { $zeile = fgetcsv ( $fp , 4096 , ";" ); <Dinge die getan werden müssen> } fclose($fp); } } ?>
Das ist die grobe Struktur für den Upload und die Verarbeitung.
Die Parameter für die Saison und die Mannschaft werden abgefragt und gespeichert. Als Speicherort wird das temp-Verzeichnis gesetzt. Damit das anschließende
move_uploaded_file
funktioniert, muss das temp-Verzeichnis existieren und über die entsprechenden Rechte verfügen. Also hier ggf. manuell via FTP anlegen und dann via CHMOD Schreibrechte geben.
Wir prüfen, ob die Datei da ist wo sie sein sollte und wenn diese Prüfung positiv ausfällt, beginnen wir damit die CSV-Datei auseinanderzunehmen.
Mittels fopen und dem Parameter „-r“ öffnen wir die Datei im NurLese-Modus. Schreiben wollen wir nichts, also reicht „-r“.
Ich definiere ein paar Variablen mit den Spaltennummern der CSV-Datei. Falls sich die Spaltenanordnung mal ändert, muss ich nur die paar Zeilen ändern; der Rest des Codes bleibt unangetastet.
Mittels
while( !feof($fp) ) { $zeile = fgetcsv ( $fp , 4096 , ";" ); <Dinge die getan werden müssen> }
„durchlaufe“ ich nun die Datei zeilenweise. fgetcsv liest jede Zeile in die Variable $zeile und baut so ein Array auf welches jede Spalte einem Element zuordnet. Das Semikolon in der Anwendung sagt dem Befehl, dass die Spalten eben mit einem Semikolon getrennt sind.
Angenommen, die erste Zeile der CSV-Datei sieht so aus:
16.08.14;15:00;1. FC Unschlagbar;SC Auswärtsschreck
Dann stehen nach dem Lesen dieser Zeile die Werte wie folgt in der Variablen $zeile:
$zeile["CSV_DATUM"] = 16.08.14 $zeile["CSV_Zeit"] = 15:00 $zeile["CSV_HOME"] = 1. FC Unschlagbar $zeile["CSV_AWAY"] = SC Auswärtsschreck
Damit lässt sich gleich wunderbar weiterarbeiten …
Ganz am Ende wird die Datei mit fclose wieder geschlossen. Wir wollen ja alles hübsch aufgeräumt hinterlassen.
Posts automatisch in WordPress anlegen
Ich habe zunächst versucht, mittels PHP und mySQL diese Daten in WordPress so zu importieren, dass sie auch funktionieren. Ich hatte auch eine „Lösung“, die funktionierte! Meine Lösung bedeutete nur, dass man massig Vorarbeiten und Nacharbeiten nach dem Import machen musste. Ohne WordPress und SQL-Insiderwissen würde das nicht gehen. Für einen „Anwender“ unzumutbar! Also machte ich mich schlau und wurde auch fündig. Letztlich wurde der Code auch noch um ein Vielfaches schlanker, als ich ursprünglich geschrieben hatte.
Denn … WordPress stellt alles zur Verfügung, was das Importer-Herz begehrt.
Der Schlüssel liegt in dem Befehl wp_insert_post. Der Link führt direkt zum Codex von WordPress, so dass du dir da in aller Ruhe die Einzelheiten ansehen kannst. In aller Kürze will ich das aber mal zusammenfassen.
wp_insert_post fügt Datensätze – also Posts – in WordPress ein. Der Clou an der Sache ist, dass man sich dabei fast keine Gedanken um WordPress-Interna machen muss, denn wp_insert_post testet selbst die Werte, die eingefügt werden sollen und vervollständigt diese auch gegebenfalls. Am allerhilfreichsten aber ist, dass der Permalink zum Post völlig automatisch gemäß den in WordPress hinterlegten Einstellungen erzeugt wird. Genau das war mein größtes Problem beim selbstgestrickten Import: die Erzeugung einer funktionierenden „guid“. Die wollte mir partout nicht gelingen und machte einige hahnebüchende Nachbearbeitungen notwendig. Mit wp_insert_post wurde das alles auf einen Schlag obsolet.
Mein „Einfügecode“ mit wp_insert_post sieht nun wie folgt aus:
$post = array( 'post_title' => utf8_encode($zeile[CSV_HOME])." vs. ".utf8_encode($zeile[CSV_AWAY]), 'post_status' => 'publish', 'post_type' => 'fixture', 'post_author' => '6', 'ping_status' => 'closed', 'post_date' => '2014-01-01', 'post_date_gmt' => '2014-01-01', 'comment_status' => 'closed' ); $post_id = wp_insert_post( $post, $wp_error );
Wie du der Dokumentation im WordPress-Codex entnehmen kannst, empfängt wp_insert_post eine ganze Menge mehr Parameter als ich sie verwende, aber mir reichen diese.
Für den Titel muss ich die Werte mittels utf8_encode bearbeiten, sonst sorgen die deutschen Umlaute für allerlei Ungemach.
Interessant ist der post_type. Übergebe ich diesen Parameter nicht, erzeugt WordPress automatisch einen Beitrag (Post). Ich habe die Spiele als Custom Post Type „fixture“ vorliegen und kann diesen Posttype gleich mitgeben. Eine Sorge weniger, denn WordPress kümmert sich automatisch um die richtigen Verknüpfungen im Hintergrund.
Eigentlich kann ich Taxonomien auch gleich mitgeben, denn mit der Mannschaft und der Saison habe ich gleich zwei Taxonomien in Gebrauch. Aus irgendeinem mir unerfindlichen Grund funktionierte das aber nicht und ich musste Alternativen suchen, die ich weiter unten beschreibe.
$post_id = wp_insert_post( $post, $wp_error );
führt den INSERT aus und liefert mir die erzeugte post_id zurück. Diese Nummer ist eindeutig! Jeder Beitrag, jede Seite hat in WordPress eine eindeutige „laufende Nummer“.
Mit Hilfe dieser ID kann ich nun noch meine weiteren Daten in WordPress vervollständigen. Die Spiele sind einer bestimmten Mannschaft und Saison zugeordnet; die einzelnen Spieldaten liegen in benutzerdefinierten Feldern, sogenannten Custom Fields. Wie bekomme ich diese Werte nun ins WordPress? Auch dafür gibts kleine Helferlein!
add_post_meta($post_id,'fixture_away_team_name',utf8_encode($zeile[CSV_AWAY])); add_post_meta($post_id,'fixture_home_team_name',utf8_encode($zeile[CSV_HOME]));
Beispielhaft sind die beiden Zeilen für den Rest meiner Custom Fields. Ich habe z.B. für jede Spielbegegnung die Felder „fixture_away_team_name“ und „fixture_home_team_name“ angelegt. Die Bedeutung der Felder ist selbsterklärend, oder? ;) Der Befehl add_post_meta fügt meinem eben eingefügten Post die beiden Custom Fields hinzu. Ich übergebe die Post_id, den Namen des Custom Fields und den Wert, der hinterlegt werden soll. In diesem Fall wieder mit utf8_encode beackert wegen Sonderzeichen, Umlauten und solchem Gedöns.
Fehlen nur noch die Saison und die Zuordnung zur Mannschaft. Jedes Fussballspiel ist einer Mannschaft und einer Spielsaison zugeordnet. Das sind in meinem Fall Taxonomien. Also wieder etwas besonderes in WordPress. Doch halb so wild, denn auch dafür gibts eine Funktion in WordPress, die mir unter die Arme greift.
wp_set_object_terms( $post_id, $auswahl, 'team' ); wp_set_object_terms( $post_id, $saison, 'season' );
wp_set_object_terms verknüpft auf leichteste Art und Weise WordPress-Posts mit Taxonomien. Ich übergebe an die Funktion wieder die ID des Beitrags, den Wert der Taxonomie sowie den Namen der Taxonomie, für welche der Wert gespeichert werden soll.
Ein Beispiel?
Die o.g. Party betrifft den 1. FC Unschlagbar und findet in der Saison 2014-2015 statt. Mittels der Auswahl in dem kleinen Formular vom Anfang habe ich dem Script gesagt, dass ich den Spielplan für den 1. FC Unschlagbar in der Saison 2014-2015 einlese. Mittels wp_set_object_terms verknüpft ich nun das eingelesene Spiel mit der Mannschaft „1. FC Unschlagbar“ und der Saison „2014-2015“. So kann ich in WordPress dann z.B. alle Spiele anzeigen lassen, die der 1. FC Unschlagbar in der Saison „2014-2015“ zu bestreiten hat. (So – damit habe ich auch gleich noch den Sinn von Taxonomien erklärt. Außerdem verspreche ich, das 1. FC Unschlagbar in diesem Artikel nicht mehr zu verwenden. *g*)
Und das funktioniert?
Noch nicht wirklich. Wenn du dir die obigen Codeschnipsel in ein eigenes PHP-Script schüttest wirst du enttäuscht sein, denn … es funktioniert nicht. Das PHP-Script kennt die ganzen Funktionen wie wp_insert_post, wp_set_object_terms und add_post_meta nämlich nicht. Wir müssen also noch WordPress selbst anzapfen. Das ist ebenfalls ganz einfach und ich habe daher nur eine Zeile nach dem startenden PHP-Tag eingefügt:
<?php include ("wp-load.php");
Da mein Script in der Root der WordPress-Installation liegt, muss ich mir keine großen Gedanken machen, wo die Datei wp-load.php auf meinem Webspace herumfährt. Wenn Du dein Script nicht in dem gleichen Verzeichnis wie die Datei wp-load.php liegen hast, musst du das include um den entsprechenden Pfad zur Datei erweitern!
Sodele … aber nun tut es.
Fazit
Es ist ein leichtes, automatisch Postings ins WordPress einzufügen. Selbst die Verwendung von Custom Post Types, Custom Fields und Taxonomien stellt keine ernste Hürde für einen umfänglichen Import von Beiträgen in eine WordPress-Seite dar.
Bis heute Mittag hatte ich keine Ahnung wie das funktionieren soll und welche Möglichkeiten mir WordPress bietet. Ich hatte nur ein Problem und die Erfahrung, dass ich nur mit großen Schwierigkeiten und kompletter Abwesenheit von Usability es schaffe, per PHP und ein bisschen mySQL Beiträge valide ins WordPress einzuspielen. Hätte ich nur gleich mal richtig gesucht. Leider finde ich mein Suchergebniss zum CSV-Import nicht mehr. Das ist schade, denn ich hätte zu gern einen Link an die hilfreichen Seite gespendet. Den richtigen Stupser zur Verwendung von wp_insert_post bekam ich von dieser Seite.
Nun denn .. hier ist der komplette Code (zumindest die wichtigen Teile):
<?php include ("wp-load.php"); if($_POST['submit'] == "Hochladen") { $saison = $_POST["saison"]; $auswahl = $_POST["auswahl"]; $name = "./temp/".$_FILES['datei']['name']; move_uploaded_file($_FILES['datei']['tmp_name'], "$name"); if(file_exists($name)) { $fp = fopen($name, "r"); define("CSV_DATUM", 0); define("CSV_ZEIT", 1); define("CSV_HOME", 2); define("CSV_AWAY", 3); while( !feof($fp) ) { $zeile = fgetcsv ( $fp , 4096 , ";" ); $post = array( 'post_title' => utf8_encode($zeile[CSV_HOME])." vs. ".utf8_encode($zeile[CSV_AWAY]), 'post_status' => 'publish', 'post_type' => 'fixture', 'post_author' => '6', 'ping_status' => 'closed', 'post_date' => '2014-01-01', 'post_date_gmt' => '2014-01-01', 'comment_status' => 'closed' ); $post_id = wp_insert_post( $post, $wp_error ); add_post_meta($post_id,'fixture_away_team_name',utf8_encode($zeile[CSV_AWAY])); add_post_meta($post_id,'fixture_home_team_name',utf8_encode($zeile[CSV_HOME])); wp_set_object_terms( $post_id, $auswahl, 'team' ); wp_set_object_terms( $post_id, $saison, 'season' ); } fclose($fp); } } ?> <h1>Spielplan-Upoad</h1> <h2>Die CSV-Datei muss wie folgt aufgebaut sein!</h2> <table> <tr><td>1. Spalte</td><td>Spieldatum im Format TT.MM.JJ</td></tr> <tr><td>2. Spalte</td><td>Spielzeit im Format HH:MM</td></tr> <tr><td>3. Spalte</td><td>Heimmannschaft</td></tr> <tr><td>4. Spalte</td><td>Auswärtsmannschaft</td></tr> </table> <form action="#" method="post" enctype="multipart/form-data"> <p> <select name="saison" size="1"> <option value="2014-2015">2014-2015</option> </select> </p> <p> <select name="auswahl" size="1"> <option value="1. Mannschaft">1. FC Unschlagbar</option> <option value="2. Mannschaft">2. Mannschaft</option> </select> </p> <p>Bitte die Datei auswaehlen !!<br> <input name="datei" type="file" size="50" maxlength="100000"> </p> <input type="submit" name="submit" value="Hochladen"> <input type="reset" value="Abbrechen"> </form>
Übrigens liefen während der ganzen Programmierei und Schreiberei an diesem Artikel „Epic Music“-Compilations auf Youtube. Ich muss sagen, da kann ich gut zu arbeiten … :)