Es ist schon etwas ernüchternd, wenn man morgens den ersten Blick in die E-Mail Konten wirft und anhand der WordPress Benachrichtigungsmails feststellen muß, daß sich wieder einmal an die hundert Spam- oder Unsinn-Kommentare in WordPress angesammelt haben. Schon an den Absenderdaten erkennt man meist überschlägig: Alles Schrott, vieles kann weg. Spätestens hier entsteht dann der Wunsch den gesamten Müll jederzeit und von überall mit einem Knopfdruck, entfernen zu können.
Vorausgesetzt wird, daß keine WordPress Plugins im Hintergrund wirken, denen man mit den nächsten Schritten ins Handwerk pfuscht. Außerdem wird angenommen, daß eingehende Kommentare per Voreinstellung als sofort freigegeben gelten. Desweiteren muß darauf hingewiesen werden, daß manuelle Eingriffe in die WordPress-Datenbankstrukturen grundsätzlich Probleme verursachen können.
Neue Kommentare werden, ergänzt mit Verwaltungsinformationen, in der Tabelle wp_comments abgelegt. Das Datenfeld wp_comments.comment_approved ist zum Beispiel mit dem Wert »1« belegt, wenn der Kommentar freigegeben wurde oder mit dem Wert »trash« belegt, wenn der Kommentar für die Löschung/Leerung des Papierkorbs vorgesehen ist. Erlaubt WordPress geschachtelte Kommentare, enthält wp_comments.comment_parent des Child-Kommentars die ID des bezogenen Parent-Kommentars, andernfalls steht hier einfach eine »0«. Innerhalb dieser Hierarchie werden die Kommentare nach Datum aufsteigend sortiert dargestellt. Die zu den Kommentaren in Bezug stehende Tabelle wp_commentmeta wurde damit bisher noch nicht berührt. Das ändert sich erst, wenn über das Web-Interface von WordPress ein Kommentar gelöscht, genauer gesagt zunächst in den Papierkorb verschoben wird. Dann laufen diese vier Dinge ab:
- in der Tabelle wp_comments wird das Feld comment_approved von »1« auf »trash« gesetzt
- die Tabelle wp_commentmeta erhält einen neuen Datensatz mit der ID des gelöschten Kommentars wp_comments.comment_ID, dem Schlüssel _wp_trash_meta_status und dem Wert des Datenfelds wp_comments.comment_approved vor dem Löschen, hier also eine »1«
- die Tabelle wp_commentmeta erhält noch einen weiteren Datensatz, ebenfalls mit der ID des gelöschten Kommentars, dem Schlüssel _wp_trash_meta_time und dem Unix-Timestamp des Löschzeitpunkts
- besonders problematisch: obwohl es sich um eine berechenbare Größe handelt ist das Datenfeld wp_posts.comment_count mit der Anzahl der zugehörigen Kommentare vorbesetzt und wird mit jedem Löschvorgang um eins dekrementiert.
Wird über die WordPress-Verwaltung in einem weiteren Schritt jetzt auch noch der Papierkorb geleert, werden alle drei betroffenen Datensätze (2 × wp_commentmeta und 1 × wp_comments) aus den beiden Tabellen gelöscht. Wenn übrigens ein Parent-Kommentar gelöscht wird, nicht aber seine Child-Kommentare, erhalten diese Child-Kommentare im Datenfeld wp_comments.comment_parent den Eintrag »0«, werden also nachträglich quasi selbst zu Parent-Kommentaren. Ihre Einsortierung in der Kommentarhierarchie erfolgt dann wiederum nach Datum. Dieses WordPress Verhalten ist überdenkenswert.
Aufgabe
Benötigt wird jetzt eine praktische Möglichkeit Kommentare eines beliebigen Zeitraums ohne Zuhilfenahme des WordPress Web-Interface zu löschen. Genauer gesagt, zunächst sollen die betreffenden Kommentare in den WP-Papierkorb verschoben werden aus dem eine Rettung vor der nächsten Leerung jederzeit wieder möglich ist. Zum Einsatz soll eine MySQL-Prozedur (stored procedure) in der WordPress Umgebung und ein kleines Skript auf dem PC oder Notebook kommen, das die entfernte Prozedur anstößt. Hier sei nochmals der Hinweis erlaubt: Keine Eingriffe in Produktionssysteme ohne Datensicherung und dann auch nur mit dem Verständnis dafür, was man da eigentlich macht.
Prozedur
Die MySQL-Prozedur kann sehr bequem über den SQL-Editor von phpMyAdmin erfaßt und abgespeichert werden:
DROP PROCEDURE IF EXISTS proc_comments_new_delete //
CREATE DEFINER = 'WP_DB_User'@'localhost'
PROCEDURE proc_comments_new_delete()
BEGIN
drop temporary table if exists WP_Database.tmp;
create temporary table WP_Database.tmp (
ID bigint(20) unsigned not null auto_increment,
c_ID bigint(20) unsigned not null default '0',
cp_ID bigint(20) unsigned not null default '0',
primary key(ID)) engine = memory;
insert into WP_Database.tmp
select NULL, co.comment_ID, co.comment_post_ID
from wp_comments as co
where date(co.comment_date) >= date_sub(current_date(),
INTERVAL 1 DAY)
and co.comment_approved = '1';
insert into WP_Database.wp_commentmeta
select NULL, tmp.c_ID, '_wp_trash_meta_status',
co.comment_approved
from tmp, wp_comments as co
where tmp.c_ID > 0 and tmp.c_ID = co.comment_ID;
insert into WP_Database.wp_commentmeta
select NULL, tmp.c_ID, '_wp_trash_meta_time',
unix_timestamp(current_timestamp())
from tmp, wp_comments as co
where tmp.c_ID > 0 and tmp.c_ID = co.comment_ID;
update WP_Database.wp_comments as co, tmp
set co.comment_approved = 'trash'
where co.comment_ID = tmp.c_ID;
call proc_posts_commentcount_update();
drop temporary table if exists WP_Database.tmp;
END //
Hinweise: Das Update der Kommentarzähler erfolgt hier in einer eigenen Prozedur, da diese Routine auch eigenständig sinnvoll eingesetzt werden kann. Die Beschreibung hierzu erfolgt im nächsten Post. Der Begrenzer/Delimiter »//« muß im SQL-Editor bekannt gemacht werden, damit der Editor die Verwaltung von der Nutzdaten zwischen Begin und End richtig trennen kann. Diese Schritte werden dabei durchlaufen:
- alte Prozedur gleichen Namens ggf. löschen
- neue Prozedur erstellen
- temporäre Tabelle tmp löschen
- temporäre Tabelle tmp mit drei Datenfeldern zur Aufnahme der IDs erstellen
- temporäre Tabelle mit den IDs der Kommentare füllen, die von heute oder gestern sind und den comment_approved Status »1« tragen
- Tabelle wp_commentmeta gemäß den IDs aus der Tabelle tmp mit den Einträgen ID, _wp_trash_meta_status und dem comment_approved Status aus der Tabelle wp_comments beschicken
- Tabelle wp_commentmeta desweiteren mit den Einträgen zu ID, _wp_trash_meta_time und dem Unix-Timestamp des Löschdatums beschicken
- in der Tabelle wp_comments das Datenfeld comment_approved von »1« auf »trash« setzen
- update des Kommentarzählers wp_posts.comment_count für alle Kommentare/Posts durchführen
- temporäre Tabelle tmp löschen
Der unter Punkt 5. genannte Zeitraum (gestern/heute) ist natürlich nur ein Beispiel und sollte dem Bedarf angepaßt werden. Alternativ könnte auch ein konkreter Zeitraum, zum Beispiel die letzten 24 Stunden, vorgegeben werden:
select comment_post_ID as PostID, comment_author as Autor,
comment_date as DatumZeit, comment_author_email as EMail,
comment_content as Inhalt
from WP_Database.wp_comments as co
where (unix_timestamp() - unix_timestamp(co.comment_date))
between 0 and 86400 and
co.comment_approved = '1'
order by co.comment_date;
Hinweis: Die »magic number« 86400 setzt sich natürlich zusammen aus 60 Sekunden × 60 Minuten × 24 Stunden.
Skript
Schließlich wird noch ein kleines Skript benötigt, das man per »rechte Maustaste | Öffnen | Klick« den eigentlichen Job machen läßt. Mehr sollte nicht notwendig sein:
@echo off
set Nachricht=alle neuen Kommentare geloescht
::
echo.Kommentare von heute und vom Vortag suchen ...
::
set sql=
set sql=%sql% call proc_comments_new_delete()
::
c:\"program files (x86)"\putty\plink -ssh -P 22 ^
-l ssh-user -pw user-password ^
hostname-oder-ip ^
/usr/bin/mysql -u mysql-user –pmysql-password wordpress-database ^
--execute=\"%sql%\"
::
if %ERRORLEVEL% gtr 0 (
set Nachricht=Fehler beim Loeschen neuer Kommentare ^
& goto batchend)
::
:batchend
echo.%Nachricht%
Zusammenfassung
Während dieser Artikel entstand sind 27 Spam-/Unsinn-Kommentare eingetroffen und komfortabel wieder entsorgt worden. Natürlich ist das Löschen ganzer Kommentargruppen gestern/heute oder 24 Stunden nicht der Weisheit letzter Schluß. Aber es ist möglicherweise eine Vorstufe zu einer zentralen, transparenten, nicht WordPress-Plugin gestützten Spam-Kontrolle. Abschließend sei nochmals angemerkt, daß Manipulationen an der WordPress Datenbank, vorbei an der WP-Datenbankverwaltung, fatale Folgen für den gesamten Blog haben kann. Desweiteren ist das Versenden von Paßwörtern und sonstigen Verbindungsdaten über unverschlüsselte Leitungen, wie hier im Skript gezeigt, sehr schlechter Stil. Es ist halt nur bequem.
rh2012-08-001