Archiv der Kategorie: Wordpress

Eine simple API für WordPress, Teil 2

Wir kommen nun dazu, mit Hilfe unserer kleinen API einen WordPress-Artikel anzulegen.

Dazu müssen wir uns kurz mit den internen Funktionen beschäftigen, die WordPress für dieses Unterfangen zur Verfügung stellt.

Es ist zunächst die Funktion “wp_insert_post“.

wp_insert_post( array $postarr, bool $wp_error = false )

Mit ihrer Hilfe kann man Artikel (oder auch Seiten) anlegen und bearbeiten.

Es gibt viele Parameter. Wir betrachten zunächst die wichtigsten:

  • post_content – der Inhalt des Artikels
  • post_title – die Überschrift des Artikels
  • post_status – der Status des Artikels und

Stellen wir uns ein simples Array zusammen:

$postarr => array(
	'post_content' => '<p>Das ist ein <b>Test äöüß</b><br>Und noch eine Zeile</p>',
	'post_title' => 'Meine erste Überschrift',
	'post_status' => 'publish'
	)

Die Werte sollten sich von selbst erklären. Ich empfehle, wenn immer möglich beim Testen Umlaute zu nutzen, um später keine bösen Überraschungen zu erleben.

Ich setze “post_status” auf “publish“, da ich die Artikel sofort veröffentliche. Wer aber noch händisch nachkontrollieren oder erstmal sichergehen möchte, der setzt “post_status” auf “draft“.

Der neue Client

Der Client muss entsprechend angepasst werden.

<?php
$username='mueller';
$password='geheim';
$serverurl='http://domain.tld/maschine/';
$postdata_array=array(
	'function' => 'wp_insert_post', 
	'wp_post_array' => array(
		'post_content' => '<p>Das ist ein <b>Test äöüß</b><br>Und noch eine Zeile</p>',
		'post_title' => 'Meine erste Überschrift',
		'post_status' => 'publish'
		)
	);

$postdata = http_build_query(
	array(
		'postdata_string' => serialize($postdata_array)
	)
);

$auth = base64_encode($username.':'.$password);
$header = array("Authorization: Basic $auth",
	'Content-type: application/x-www-form-urlencoded'
	);

$opts = array('http' =>
	array(
		'method'  => 'POST',
		'header'  => $header,
		'content' => $postdata,
		'timeout' => 5000,  //5 Sekunden
	)
);

$context  = stream_context_create($opts);
$result = file_get_contents($serverurl, false, $context);

header('Content-Type: text/html; charset=utf-8');
print $result;
exit();
?>

Der Aufbau hat sich nicht sehr gegenüber Teil 1 verändert.

Der neue Server

Beim Server hat sich ein wenig mehr getan.

<?php
define('WP_USE_THEMES', false);
header('Content-Type: text/html; charset=utf-8');
if (count($_GET)>0) $postdata_array=unserialize(array_shift($_GET));
if (count($_POST)>0) $postdata_array=unserialize(array_shift($_POST));

require('../wp-load.php');

if ($postdata_array['function']=='wp_insert_post') {
	print wp_insert_post($postdata_array['wp_post_array']);
}
exit();

Wir müssen nun die Verbindung unseres Servers zu WordPress herstellen. Das passiert ganz einfach durch die Zeile:

require('../wp-load.php');

Die Datei “wp-load.php” die sich im Hauptverzeichnis des Blogs befindet, stellt automatisch alle Daten und Komponenten zur Verfügung, die wir für den Umgang mit WordPress benötigen.

Die Zeile:

define('WP_USE_THEMES', false);

Sorgt noch dafür, dass wir keine Komponenten laden, die lediglich für das Frontend des Blogs benötigt werden.

Der Rest dürfte sich von selbst erklären. Interessant ist Zeile 10, in der der eigentliche Befehl zum Anlegen ausgeführt wird und als Ergebnis die ID des neuen Artikels (WordPress-Jargon: Post) an den Client zurückgegeben wird. Diese ID ist wichtig und sollte irgendwo auf Seiten des Clients gespeichert werden, denn später werden wir sie benötigen, um Artikel zu korrigieren, zu löschen oder mit anderen Daten anzureichern. Die ID ist immer eindeutig und wird niemals doppelt vergeben.

Rufen wir den Client auf:

http://localhost/client.php

Das Ergebnis gibt uns die ID 4025 zurück.

Und schauen wir mal ins Backend des Blogs.

Voilà – Das ist unser Artikel.

Bis dahin war es noch nicht ganz so schwer. Bisher hatten wir nur die Pflicht, jetzt folgt die Kür. Im nächsten Teil werde ich zeigen, wie man dem Artikel auch ein Bild zuordnet.

Eine simple API für WordPress, Teil 1

Ich benötig(t)e eine Methode, um von ferne (remote) meine verschiedenen WordPress-Blogs betreiben zu können. Artikel, Posts etc. sollen von einer Datenbank gefüttert werden.

Dazu hat es früher einmal im Kern von WordPress eine REST-API gegeben, die aber abgeschaltet wurde. Demnächst (ab WP 4.7) soll sie wieder implementiert werden. Ich habe mir die Dokumentation des Beta-Plugins durchgelesen und finde sie wie immer viel zu lang. Es dauert ewig, bis jemand auf den Punkt kommt. Typisch doppelter Overhead der OOP-verliebten Informatiker was Code und Dokumentation betrifft.

Auch wollte ich mich nicht auf eine Beta-API verlassen, deren Schnittstellen offen vorlegen. Da kann ich die Zeit stoppen, bis die ersten Hacker sich drauf einstellen. Dann eine Lücke und zack! ist mein Blog gekapert.

Also muss eine eigene Lösung her. WordPress liefert intern alle möglichen, bequemen Methoden, um Artikel anzulegen, zu bearbeiten oder zu löschen. Mehr brauchen wir erstmal nicht. Die Kunst besteht nur darin, diese Methoden anzusprechen.

Also bauen wir uns unsere eigene API. Wir benötigen:

  1. einen Server im WordPress-Blog, der auf Kommandos wartet, diese verarbeitet und Ergebnisse zurückmeldet.
  2. einen Client, der Befehle sendet, wartet und die Antworten des Servers auswertet.
  3. und natürlich sollte der Server geschützt sein, damit er nicht gehackt wird.

Der Server

Zunächst legen wir im Hauptverzeichnis des WordPress-Webservers einen Unterverzeichnis an. Ich nenne diese Verzeichnis “maschine” – aber ihr könnt auch jeden anderen eindeutigen Namen nehmen.

md maschine

(Natürlich kann man das Verzeichnis auch per FTP anlegen, wenn ihr keine Kommandozeile öffnen könnt.)

Nun heißt es erst einmal, das Verzeichnis gegen Unbefugte abzusichern. Das geht am einfachsten mit einer .htpasswd-Datei. Diese kann man online generieren. Dann einfach nur ins Verzeichnis “maschine” kopieren.

Mit der User/Passwort-Kombination mueller/geheim sieht die Datei folgendermaßen aus:

mueller:$apr1$rgTdOKDd$3SFDZoE.N1QNkzmHXmOKe1

Aufpassen: vor dem htpasswd steht ein Punkt, das macht die Datei normalerweise unsichtbar, daher muss man das Anzeigen von versteckten Dateien im FTP-Programm aktivieren.

Nun braucht es noch eine .htaccess-Datei mit folgenden Einträgen:

AuthName "beliebig"
AuthType Basic
AuthUserFile /var/www/domain.tld/maschine/.htpasswd
require valid-user

Der Eintrag in AuthUserFile kann bei euch natürlich ein anderer sein. Wichtig ist, dass er den absoluten Pfad (vom root des Servers aus) zur eben angelegten .htasswd-Datei beinhaltet. Diesen Pfad könnt ihr bei eurem Provider erfragen oder in den FAQ nachlesen. Meist liefern die Provider auch bequeme Funktionen (Stichwort: Verzeichnisschutz) gleich mit, um alles online zu generieren.

Nun ein kleiner Test. Wir rufen im Browser auf:

http://domain.tld/maschine/

Ab jetzt steht “domain.tld” natürlich für eure eigene Domain. Jetzt müsste sofort die Abfrage nach User und Passwort erfolgen. Wenn nicht, habt ihr etwas falsch gemacht. Dann nochmal von vorn, denn Sicherheit, besonders im produktiven Bereich ist wichtig.

In Zukunft erfolgt der Aufruf natürlich automatisiert, da ist eine Passwort-Eingabe nicht möglich. Zu diesem Punkt kommen wir gleich.

Jetzt programmieren wir den einfachsten Server. Dazu legen wir eine Datei “index.php” im Verzeichnis “maschine” an, die demnächst unsere Befehle verarbeitet.

<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_GET['befehl'])) $befehl=$_GET['befehl'];
if (isset($_POST['befehl'])) $befehl=$_POST['befehl'];
print mb_strtoupper($befehl);
exit();
?>

Wir nutzen die multibyte-fähigen Funktionen, die man am Präfix “mb_” erkennt, damit wir später keine Probleme mit Umlauten haben. Unseren kleinen Server kann man schon per Get-Parameter in der URL oder per Post (bspw. bei Formulareingaben) bedienen. Zurückgegeben wir ein einfacher utf8-kodierter Text. Das ist für unsere Belange absolut ausreichend und bietet bessere Debug-Möglichkeiten.

Was macht der Server? Er nimmt einen Parameter (Hier “befehl”) auf und wandelt den String in Großbuchstaben um. Das ist natürlich ein wenig sinnfrei, aber wir wollen ja erstmal testen.

Wir rufen also im Browser auf:

http://mueller:geheim@domain.tld/maschine/?befehl=hallo

Mit den Angaben vor dem “@” kann man User und Passwort gleich in der URL übergeben und muss diese nicht immer extra eingeben. Und wenn alles geklappt hat, müsste im Browser der Text “HALLO” erscheinen.

Der Client

Jetzt geht es an einen kleinen, ersten Client. Wir legen eine Datei an, der Name ist egal, ich nenne sie “client.php”. Diese Datei kann irgendwo angelegt sein, sinnvollerweise nicht auf dem selben Server, auf dem unser API-Server läuft, denn sonst wäre die Arbeit ja sinnlos.

<?php
$username='mueller';
$password='geheim';
$serverurl='http://domain.tld/maschine/';
$befehl="hallo nochmal äöüß";

$postdata = http_build_query(
array(
'befehl' =>&nbsp;$befehl
)
);

$auth = base64_encode($username.':'.$password);
$header = array("Authorization: Basic $auth",
	'Content-type: application/x-www-form-urlencoded'
	);

$opts = array('http'&nbsp;=>
array(
'method' =>&nbsp;'POST',
'header' =>&nbsp;$header,
'content' =>&nbsp;$postdata,
'timeout' =>&nbsp;5000
)
);

$context = stream_context_create($opts);
$result = file_get_contents($serverurl, false, $context);

header('Content-Type: text/html; charset=utf-8');
print $result;
exit();
?>

Das sieht schon etwas komplizierter aus. Im Grunde simulieren wir das, was wir vorhin im Browser händisch gemacht haben: Wir packen Daten zusammen (hier nur $befehl), authentifizieren uns am Server und geben die Rückgabe wieder aus.

Zeile 2+3 sind selbsterklärend. WICHTIG: Ohne das abschließende Slash in Zeile 4 wird es nicht funktionieren – also nicht vergessen.

Zeile 5 beinhaltet den “Befehl”, den wir absenden, wieder erstmal nichts besonderes.

Der Rest der Zeilen dient dazu, einen Stream zu generieren und damit das PHP-Skript zu einem kleinen Browser zu machen.

Zeile 21 legt einen Timeout in Millisekunden fest. Da die meisten Hoster nur 30 Sekunden an Ausführungszeit für ihre Skripte zulassen, lohnen sich Werte über 30 Sekunden gar nicht erst, denn der Server wird bereits vorher abbrechen.

Wir machen den Test und rufen den Client im Browser auf:

http://localhost/client.php

Etwas smarter, bitte!

Nun ist es auf Dauer etwas unbefriedigend, immer nur $befehl=blabla an den Server zu senden, daher machen wir jetzt die Datenübermittlung etwas smarter. Und zwar so, dass im Prinzip alles gesendet und empfangen werden kann, solang wir es in Variablen verpacken können.

Dazu passen wir zunächst den Client an:

Zeile 5 wird zu:

$werte_array=array(3, 'Test äöü', 'farbe' => 'rot');

und Zeile 9 zu:

'werte_array' => serialize($werte_array)

Und der Server wird auch angepasst.

Die Zeilen 3, 4 und 5 werden zu:

if (count($_GET)>0) $werte_array=unserialize(array_shift($_GET));
if (count($_POST)>0) $werte_array=unserialize(array_shift($_POST));
print_r ($werte_array);

Welchen Sinn hat das? Wir packen alle Variablen, die wir übertragen wissen wollen, in ein Array, serialisieren es mit serialize und packen es auf der anderen Seite wir mit unserialize wieder aus. Der Name des zu transportierenden Arrays spielt dabei keine Rolle; array_shift ermittel automatisch das richtige Array.

Später werden wir serialize auch nutzen, um mehr als nur ein simples “Hallo Welt” an den Client zurückzuliefern. Aber fürs erste sollte das genügen.

Wir fassen zusammen

Server

<?php
header('Content-Type: text/html; charset=utf-8');
if (count($_GET)>0) $werte_array=unserialize(array_shift($_GET));
if (count($_POST)>0) $werte_array=unserialize(array_shift($_POST));
print_r ($werte_array);
exit();
?>

Client

<?php
$username='mueller';
$password='geheim';
$serverurl='http://domain.tld/maschine/';
$werte_array=array(3, 'Test äöü', 'farbe' => 'rot');

$postdata = http_build_query(
array(
'werte_array' =>&nbsp;serialize($werte_array)
)
);

$auth = base64_encode($username.':'.$password);
$header = array("Authorization: Basic $auth",
	'Content-type: application/x-www-form-urlencoded'
	);

$opts = array('http'&nbsp;=>
array(
'method' =>&nbsp;'POST',
'header' =>&nbsp;$header,
'content' =>&nbsp;$postdata,
'timeout' =>&nbsp;5000, //5 Sekunden
)
);

$context = stream_context_create($opts);
$result = file_get_contents($serverurl, false, $context);

header('Content-Type: text/html; charset=utf-8');
print $result;
exit();
?>

Wenn wir jetzt den Client wieder aufrufen:

http://localhost/client.php

erhalten wir:

Array ( [0] => 3 [1] => Test äöü [farbe] => rot )

Und mehr benötigen wir für heute nicht. Der Server hat also das Array richtig empfangen.

Im nächsten Teil werden wir dann unseren kleinen Server dazu nutzen, um einen WordPress-Artikel anzulegen.

Neuartiger Angriff auf WordPress über umbenannte wp-config-Dateien

Heute hat so ein Arschloch aus der Ukraine (IP: 91.202.145.143) versucht, verschiedene meiner WordPress-Installation zu hacken, indem versuchte direkt auf eine Koprie der wp_config-Datei zuzugreifen, in der die MySQL-Zugangsdaten abgespeichert sind.

Eine einfacher Trick: Er hat einfach die beliebtesten Kombinationen für Dateiumbenennungen ausprobiert:

wp-config.php.swp
wp-config.php-bak
wp-config.txt
wp-config.php.old
wp-config.bak
wp-config.php.save
wp-config.php.bak

Wer also seinen Webserver nicht für andere Files außer HTML, JPG, PHP etc. dicht gemacht hat, sollte alle Backups der wp-config-Datei löschen.

Smarte Suche für MySQL und WordPress mit mehreren Suchwörtern

Die eingebaute Suche von WordPress ist alles andere als gut, versagt sie doch schon bei mehr als einem Suchwort.

Bevor man aber gleich wieder Plugins installiert, kann man die Sache auch händisch programmieren.

Dazu muss zunächst in MySQL ein Volltext-Suchindex für die relevanten Datenspalten erzeugt werden:

ALTER TABLE `wp_posts` ADD FULLTEXT `MyFullTextIndex` (`post_title`, `post_content`)

Wie man sehen kann, bevorzuge ich die Suche in den Titeln und dem gesamten Textkörper. Wem das aber zu viel des Guten ist, kann es auch auf die Textzusammenfassung begrenzen:

ALTER TABLE `wp_posts` ADD FULLTEXT `MyFullTextIndex` (`post_excerpt`)

Nun kann man ganz einfach gewichtet suchen:

SELECT ID, post_title, MATCH (post_title, post_content) AGAINST ('Suchwort1 Suchwort2 Suchwort3') AS score

FROM wp_posts

WHERE MATCH (post_title, post_content) AGAINST ('Suchwort1 Suchwort2 Suchwort3')

AND post_status = 'publish'

Bingo!

WordPress plugin: Display SQL Stats

Displaying SQL result data as graphical chart on the dashboard with use of Google Chart Tools.

Showing chart in dashboard

Showing chart in dashboard

Settings

Settings

Download from WordPress repository.

Changelog

0.7 (2014/02/05)

  • adding/removing SQL statements more stylish

0.6.5 (2013/12/27)

  • fix bug on automatic detection of column types
  • better formatting of debug information

0.6.4 (2013/11/08)

  • only enrich header when on dashboard

0.6.3.1 (2013/10/17)

  • fixing small bug on min/max values when using more than one barcharts

0.6.3 (2013/10/15)

  • placing automatic max/min values on barcharts for better visualisation

0.6.2 (2013/09/14)

  • bug fix on emtpy results
  • switch charts on/off

0.6.1 (2013/09/14)

  • bug fix on wrong date display

0.6 (2013/09/14)

  • Check on WordPress 3.6.1
  • New chart types: Bar Chart
  • auto detecting of column types: date, number, string

0.5 (2013/09/10)

  • Use different chart types (Pie Chart, Scatter Chart, Bubble Chart)

0.4 (2013/08/21)

  • Use multiple SQL statments and charts

 0.3 (2013/08/17)

  • Tested with WP 3.6
  • New default demo SQL to demonstrate how to insert additional columns

0.2 (2013/04/25)

  • NEW: Notepad function to store different SQL statement, ideas or whatever

0.1 (2013/04/16)

  • first test version