senäh

17senäh und so…

Wordpress Logo

PHP, Server & Config, WordPress
09. Jul 2012
Kommentare: 2

Blogpost-Metadaten auslesen

Kategorien: PHP, Server & Config, WordPress | 09. Jul 2012 | Kommentare: 2

Serie: Effizienteres Bloggen mit WordPress dank XML-RPC

Ok, was haben wir bisher? Eine index.php, die wie folgt aussieht:

<?php
require_once 'inc/EnnoAutoPost.php';

$htmlString = $_SERVER['KMVAR_temp'];
$obj = new EnnoAutoPost($htmlString);

Außerdem die eingebundene Klasse EnnoAutoPost:

<?php
require_once 'IXR_Library.php';

class EnnoAutoPost
{
    public function __construct($htmlString)
    {
        // do something
    }
}

Und was haben wir vor? Wir wollen die Metadaten auslesen und entsprechend speichern. Welche Metadaten? Konkret: den Titel, den Slug (quasi der Permalink), Tags, Kategorien und den Excerpt/Auszug. Aber fangen wir zunächst mal mit dem Einfügen ein paar weniger Variablen an.

Variablen der EnnoAutoPost-Klasse

Zuerst legen wir Variablen für jede der genannten Meta-Informationen an. Außerdem speichern wir den Inhalt des Blogposts in HTML-Form in einer Variable ($_content). Da wir im weiteren Verlauf des Skripts auch mehrfach eine Verbindung über die XML-RPC-Schnittstelle herstellen werden, ist es sinnvoll die dazugehörige Instanz in eine Variable zu speichern ($_client). In diesem Zug können wir auch gleich die URL der Schnittstelle als erste Konfigurationsvariable speichern ($_url). Zu guter letzte noch eine generische Variable $_postData für das finale Sammeln der zu sendenden Blogpost-Daten. Über deren Verwendung lässt sich im Nachhinein sicherlich aus Redundanz-Gründen streiten, aber es ist erstmal übersichtlicher.

Da wir etwas mit regulären Ausdrücken arbeiten werden, habe ich mich außerdem für einen zentralen Begrenzer (DELIMITER) entschieden. Damit umgehe ich etwas “Escaperei”, was die ohnehin schwer lesbaren Regex-Patterns noch etwas kryptischer machen würde. Der Kopf unserer Klasse dürfte also anschließend so aussehen:

<?php
require_once 'IXR_Library.php';

class EnnoAutoPost
{
    /**
     * config
     */
    private $_url = 'http://www.ienno.de/xmlrpc.php';

    /**
     * constants
     */
    const DELIMITER = '|';

    /**
     * internals
     */
    private $_client;
    private $_title;
    private $_content;
    private $_slug;
    private $_tags;
    private $_categories;
    private $_excerpt;
    private $_postData = array();

    ...
}

In der __construct-Methode sorgen wir außerdem dafür, dass der bei der Instanzierung der Klasse übergebene HTML-String gespeichert wird. Außerdem instanzieren wir wie bereits angesprochen den XML-RPC-Client.

public function __construct($htmlString)
{
    $this->_client = new IXR_Client($this->_url);
    $this->_content = $htmlString;
}

Metadaten im Text notieren

In welcher Syntax halte ich die Metadaten in meinem Blogpost fest? Recht easy:

@slug: TODO
@tags: TODO
@categories: TODO
@excerpt: TODO

Das TODO dient als Indikator, damit wir nicht vergessen, die Metadaten tatsächlich auch zu befüllen.

Es gibt zwar sowas wie Markdown Metadaten, allerdings kommen diese für dieses Skript nicht in Frage. Grund? Das Skript nimmt HTML entgegen, das durch Byword generiert wurde (siehe vorheriger Teil der Serie). Markdown Metadaten werden beim Generieren des HTML-Outputs jedoch nicht mit übernommen. Sie dienen lediglich zur Beschreibung des Markdown-Textes, nicht des HTML-Outputs. Darum also oben genannte Variante von mir mit der @-Syntax.

Metadaten auslesen

Das Auslesen erfolgt über Regex, also reguläre Ausdrücke. Dabei durchsuchen wir das HTML z.B. nach @slug, speichern den Wert in einer Variable und löschen anschließend die Medadaten aus dem HTML. Dort haben sie ja auch nichts weiter verloren.

Doch wie setzen wir das jetzt konkret um? Erstmal eine neue Methode namens setMetadata hinzufügen. Diese rufen wir in der index.php direkt nach der Instanzierung auf:

$htmlString = $_SERVER['KMVAR_temp'];
$obj = new EnnoAutoPost($htmlString);
$obj->setMetadata();

In der Methode selbst legen wir ein Array an, das als Schlüssel die zu beschreibenden Variablennamen und als Werte die Regex-Patterns für die Metadaten enthält.

$data = array(
    '_title' => '<h1 id=".*">(.*)</h1>', // for Byword versions < 1.5.2
    '_slug' => "@slug: (.*)",
    '_excerpt' => "@excerpt: (.*)",
    '_tags' => "@tags: (.*)",
    '_categories' => "@categories: (.*)"
);
$this->_extractPostMetadata($data);

UPDATE: seit Byword in Version 1.5.2 bekommen Überschriften nicht mehr automatisch IDs zugewiesen. Deswegen muss in $data der reguläre Ausdruck im Schlüssel ‘_title’ geändert werden.

$data = array(
    '_title' => '<h1>(.*)</h1>', // for Byword versions >= 1.5.2
    '_slug' => "@slug: (.*)",
    '_excerpt' => "@excerpt: (.*)",
    '_tags' => "@tags: (.*)",
    '_categories' => "@categories: (.*)"
);

Anschließend wird dieses Array an eine private Funktion (_extractPostMetadata) übergeben, in der die Regex-Magie vollzogen wird. Was heißt das genau? Nichts weiter, als dass z.B. der Slug, der sich hinter @slug versteckt, in die Variable $this->_slug gespeichert wird.

Danach müssen wir sicherstellen, dass jede der Metadaten auch wirklich gesetzt ist bzw. etwas anderes als den Startwert (TODO) enthält. Ist dem nicht so, wird das Skript mit einer entsprechenden Nachricht abgebrochen. Andernfalls wird der Wert dem Array $this->_postData hinzugefügt.

if (is_null($this->_slug) && $this->_slug != 'TODO')
    exit("slug missing");
else
    $this->_postData['post_name'] = $this->_slug;

Die Namen der Schlüssel von $this->_postData kommen dabei nicht von ungefähr, sondern beziehen sich auf die Felder in der entsprechenden Datenbank-Tabelle der WordPress-Installation. Mögliche Schlüssel sind hier unter content aufgelistet.

Eine Besonderheit gibt es bei den Tags und Kategorien zu beachten. Da es hier mitunter mehrere gibt, die im Kopf durch ein Komma und Leerzeichen getrennt notiert werden können (also @tags: Tag1, Tag2, Tag3), müssen wir hier leicht abgewandelt vorgehen. Mithilfe von explode() extrahieren wir die einzelnen Tags bzw. Kategorien und geben sie an die Funktion $this->_addTaxonomyItems weiter. Dort werden die Taxonomy-Items, wie sie in WordPress heißen, hinzugefügt.

if (is_null($this->_tags) && $this->_tags != 'TODO')
    exit("tags missing");
else {
    $taxonomyName = 'post_tag';
    $cats = explode(', ', $this->_tags);
    $this->_addTaxonomyItems($taxonomyName, $cats);
}

Nochmal in Ruhe, bitte!

Ok, hier der gesammelte Code für die setMetadata()-Methode:

public function setMetadata()
{
    $data = array(
        '_title' => '<h1 id=".*">(.*)</h1>',
        '_slug' => "@slug: (.*)",
        '_excerpt' => "@excerpt: (.*)",
        '_tags' => "@tags: (.*)",
        '_categories' => "@categories: (.*)"
    );
    $this->_extractPostMetadata($data);

    // title
    if (is_null($this->_title) && $this->_title != 'TODO')
        exit("title missing");
    else
        $this->_postData['post_title'] = $this->_title;

    // slug
    if (is_null($this->_slug) && $this->_slug != 'TODO')
        exit("slug missing");
    else
        $this->_postData['post_name'] = $this->_slug;

    // excerpt
    if (is_null($this->_excerpt) && $this->_excerpt != 'TODO')
        exit("excerpt missing");
    else
        $this->_postData['post_excerpt'] = $this->_excerpt;

    // tags
    if (is_null($this->_tags) && $this->_tags != 'TODO')
        exit("tags missing");
    else {
        $taxonomyName = 'post_tag';
        $cats = explode(', ', $this->_tags);
        $this->_addTaxonomyItems($taxonomyName, $cats);
    }

    // categories
    if (is_null($this->_categories) && $this->_categories != 'TODO')
        exit("categories missing");
    else {
        $taxonomyName = 'category';
        $cats = explode(', ', $this->_categories);
        $this->_addTaxonomyItems($taxonomyName, $cats);
    }
}

Die private Methode _extractPostMetadata ist ein bisschen komplizierter. Ich versuche mal mit kommentiertem Quellcode zu punkten:

private function _extractPostMetadata($data)
{
    // go through each key-value-pair in array
    foreach ($data as $prop => $pattern) {

        // wrap pattern with delimiters
        $pattern = self::DELIMITER . $pattern . self::DELIMITER;

        // find matches for current pattern in html-string
        preg_match($pattern, $this->_content, $matches);

        // is there a match?
        if (isset($matches[1])) {

            // set variable (transmitted as key of array $data)
            $this->$prop = $matches[1];

            // now that we saved it, delete metadata from html-string
            $this->_content = trim(preg_replace($pattern, '', $this->_content));
        }
    }

    // clean whitespace and empty paragraphs
    $pattern = "<p>\n+</p>\n*";
    $this->_content = preg_replace(
        self::DELIMITER . $pattern . self::DELIMITER,
        '',
        $this->_content
    );
}

Relativ einfach hingegen ist die _addTaxonomyItems. Dort wird einfach nur geschaut, ob das Ziel-Array schon vorhanden ist. Falls ja, wird ein Schlüssel-Werte-Paar hinzugefügt, falls nicht, wird es halt vorher noch angelegt.

private function _addTaxonomyItems($key, $data)
{
    if (isset($this->_postData['terms_names']))
        $this->_postData['terms_names'][$key] = $data;
    else
        $this->_postData['terms_names'] = array($key => $data);
}

Zugegeben, hier war ich etwas faul. Ich gehe fest davon aus, dass es hier noch eine bessere Variante gibt. Hinweise sind willkommen.

Zusammenfassung

Die ein oder andere Zeile Code mag nicht intuitiv von jedem verstanden werden. Ich hoffe jedoch, dass meine Erklärungen ausreichen, um euch ein Bild von den stattfindenden Prozessen zu vermitteln.

Falls ihr Fragen habt, haut sie einfach in die Kommentare 🙂 Im nächsten Teil geht es dann weiter mit dem automatischen Bild-Upload.

Autor: Enno

Ich bin Enno. PHP ist mein Ding, aber auch alles Neue rund um die Themen HTML5, CSS3 & Co finde ich interessant. Ich mag es Leuten zu helfen und mein Wissen weiterzugeben. Sollte dir mein Beitrag gefallen haben, lass doch nen Kommentar da oder benutze einen der Social Buttons, um deinen Dank auszudrücken ;)