Free Tool: Backlinks und PR_SUM ermitteln

Zugegeben, es gibt eine große Menge an Tools, mit denen man die Backlinks einer Domain ermitteln kann, doch keines konnte die Summe des PR ermitteln. Aus diesem Grund habe ich dieses kleine Tool geschrieben, das ich heute zum freien Download anbiete.

Warum PR-SUM?

Um den (ideellen) Wert einer (beispielsweise Expired Domain) Domain zu ermitteln, ist man auf Daten von Majestics oder MOZ angewiesen. Diese Dienste, zum Teil kostenpflichtig, schicken Spider durch das Netz und ermitteln so Links, die von einer Domain abgehen. Gleichzeitig lassen sich so für eine Domain ermitteln, welche Links auf eine Domain zeigen. Und gute Links, die auf eine Domain zeigen, machen eine Domain für die Übernahme interessant.

PR_SUM
PR_SUM – eine .NET-Anwendung, um schnell die Backlinks inkl. PR zu ermitteln.

Doch nicht jeder Link ist ein guter Link. Wir wollen keine Links von Spam-Seiten. Und viele Menschen sind nach wie vor der Meinung, dass nur ein hoher PageRank einer Domain, von der der Links kommt, auch gut für die Ziel-Domain ist. Inzwischen hat Google bekanntgegeben, dass der „öffentliche“ PageRank nicht mehr aktualisiert ist. Dies bedeutet also, dass der PR, der angezeigt ist, inzwischen veraltet ist. Dennoch kann die Summe des PR als Vergleich herangezogen werden. Einfach aufgrund der These, dass sich der PR von vielen Seiten im Laufe der Zeit nicht so stark verändern wird bzw. verändert hat.

Wenn also eine Seite ein PR_SUM von 100 hat, eine andere Seite ein PR_SUM von 90 und eine Seite ein PR_SUM von 5 – was bedeutet das? Nun, der PR_SUM 100 und PR_SUM 90 kann man als gleichwertig ansehen. Daraus lassen sich keine Schlüsse ziehen. Hier muss eine manuelle Analyse der Links erfolgen. Einfacher ist die Unterscheidung zwischen PR_SUM 100 und PR_SUM 5. Hier würde ich die Domain mit PR_SUM 100 bevorzugen, WENN ALLE anderen Werte ähnlich sind. Allerdings kann man im Umkehrschluss NICHT sagen, dass die Domain mit PR_SUM 5 schlecht ist.
Es handelt sich einfach um einen weiteren Faktor, den man bei einer Domainbewertung mit einbeziehen kann.

Wie wird der PR_SUM berechnet?

Zuerst bezieht das Tool alle Links, die über die kostenlose Schnittstelle von Mozscape API zur Verfügung gestellt werden. Dann werden die doppelten Links von einer Domain herausgefiltert. Es bleibt nur ein Link pro Domain übrig (der mit dem höchsten PR). Im Anschluss wird die Summe der verbleibenden Links summiert.

Installation der Anwendung

Bei der Anwendung handelt es sich um eine Windows .NET-Anwendung. Es muss zwingend das .NET-Framework 4.5 auf dem Rechner vorhanden sein. Die Anwendung besteht aus einer Exe und einem Ini-File. Dieses Ini-File muss im gleichen Ordner wie die Exe verbleiben. Bitte nun das File „config.ini“ mit einem einfachen Texteditor (Notepad) öffnen. Im oberen Abschnitt unter Einstellung sehen wir nun die Zeilen „strAccessID=“ und „strPrivateKey=“ . Dahinter müssen nun die persönlichen MOZ-API-Daten eingegeben werden. Hierzu ist eine kostenlose Anmeldung bei MOZ notwendig. Geht dazu auf folgende Seite: https://moz.com/products/api/keys

Nach der Anmeldung seht Ihr bei MOZ diese Werte. Kopiert sie an die entsprechende Stelle in die Config.

Schritt 2: PR-Proxies erstellen

Die Anwendung ermittelt über ein PHP-Skript den PR. Im gleichnamigen Ordner findet Ihr die zwei Files „check_pr.php“ und „PRclass.php“. Kopiert diese Files auf Webspace Eurer Wahl (und Zugriffsmöglichkeiten). Der Zugriff auf die Schnittstelle von Google wird über diesen Webaufruf erfolgen. Somit fungiert das Web als Proxy. Da die Google-Schnittstelle nur eine bestimmte Anzahl von Abfragen zulässt, macht es Sinn, das Skript auf mehreren verschiedenen Hosts (unterschiedliche IPs) zu installieren und jede Installation in der Config.ini einzutragen. Das Skript arbeitet so nacheinander bei der Abfrage alle eingetragenen Pfade ab und verteilt somit die „Last“ auf die unterschiedlichen Hosts. Ich selbst habe die beiden PHP-Dateien jeweils in einem eigenen Unterverzeichnis auf drei verschiedenen Hosts installiert. Meine Config sieht an dieser Stelle ungefähr so aus:

[PHPHOSTS]
1=http://mein_host_1.de/dsj43rd/check_pr.php
2=http://mein_host_2.de/dHAgs6sj43rd/check_pr.php
3=http://mein_host_3.de/dsjK36bsd43rd/check_pr.php

Jeder Host wird mit einer Laufnummer (1..n) gekennzeichnet. Dann folgt der komplette Pfad zum PHP-Skript. Wie Ihr sehen könnt, habe ich das Skript in einem kryptischen Unterverzeichnis angelegt. Dies soll einfach verhindern, dass ein Crawler zufällig auf das Skript stößt (kann man machen, muss man aber nicht).

Schritt 3: Testen der Anwendung

Mit einem Klick auf die Exe sollte sich das Tool öffnen. In der oberen Box wird die Domain (ohne http) eingegeben. Nach einem Klick auf Check (oder Return) beginnt die Abfrage der MOZ-API; jeweils in 50´er Blöcken. Wenn alle Daten abgefragt sind, berechnet das Tool die Daten und gibt Die Summierungen unter der Tabelle aus.

Tipps für den Umgang

Sämtliche Daten sind über die Windows-Zwischenablage kopierbar. Die Tabelle ist zudem noch mit einem Klick in den Spaltenköpfen sortierbar.

Nun folgt ENDLICH der Download-Link:
Download PR_SUM

WordPress: Plugin ohne Dashboard deaktivieren

Muss man in WordPress ein Plugin ohne Dashboard, also über die WordPress-Oberfläche deaktivieren? Was man schnell verneinen möchte, hat einen ernsten Hintergrund: Wenn ein Security-Plugin dem Admin das Login verweigert, kann das Entfernen des Plugins über die Datenbank die letzte Hoffnung sein.

Normalerweise kommt man nicht in die Versuchung ein Plugin ohne das WordPress-eigene Dashboard zu installieren oder deinstallieren. Zu einfach ins dies – nur wenige Klicks genügen. Dennoch war dies die einzige Hoffnung, eine Kundeninstallation vor WordPress schnell wieder zugänglich zu machen.

Plugin-Limit-Attempts
Das Plugin „Limit Attempts“ von BestWebSoft sperrte den Admin vom Backend aus. Das WordPress-Plugin musste also über die Datenbank deaktiviert werden. Als bisher stabil zeigt sich das Plugin „Limit Login Attempts“ von Johann Eanfeldt.

Folgendes war passiert:
Ich persönlich bei kein Freund von Plugins wie „Limit Login Attempts“. Diese Plugin verhindert, dass jemand durch mehrmaliges probieren sich Zugang über die WordPress-Login-Box verschafft. Nach drei Fehlversuchen wird die IP für einen gewissen Zeitraum gesperrt. Wenn jemand wirklich ein Brute-Force Attacke auf einen WP-Blog starten möchte, wird er sicherlich wechselnde Proxy-Server nutzen. Der bessere Ansatz ist, dass die Login-Box erst gar nicht einem Hacker zur Verfügung steht. Ansatzpunkte zum besseren Schutz der WordPress Installation habe ich in meinem Artikel „WordPress absichern: 10 Tipps für ein sicheres WP“ beschrieben.

Doch zum vorliegenden Fall zurück. Das „Limit Login Attempts“-Plugin von Johan Eanfeldt wird meist auch über die WordPress-CPanel-Installationen mit angeboten. Das Plugin ist meiner Meinung nach stabil und schadet nicht. Über seinen Nutzen kann man auf jeden Fall diskutieren. Im vorliegenden Fall war jedoch nicht das Plugin von Johan Eanfeldt, sondern das Plugin „Limit Attempts“ von „BestWebSoft“ installiert gewesen. Und nach der Installation machte das Plugin den Zugang absolut sicher: Auch der Admin hatte keinen Zugriff mehr auf das Dashboard. Ohne Fehlermeldung (im entsprechenden HTML-Container war kein Hinweis ersichtlich) blockierte das Plugin den WP-Zugang. Das Passwort war richtig. Auch eine Passwort-Änderung brachte kein Erfolg. Ebenso wenig das Hinzufügen eines neuen Admins über die Datenbank: Das Plugin „Limit Attempts“ sperrte jeden Benutzer aus.

Es war zu diesem Zeitpunkt nicht sicher, dass es an diesem Plugin lag. Doch der Verdacht lag nahe und eine Lösung musste her, das Plugin zu deaktivieren und wieder einen Zugriff auf das Backend zu gewähren. Die Idee, das Plugin per FTP einfach zu löschen, habe ich verworfen. Denkbar, dass dies auch zum Erfolg geführt hätte: Man müsste dies einmal mit einer Testinstallation ausprobieren. Sauberer Weg ist sicherlich das Deaktivieren des Plugins. Und da der Zugriff auf die Datenbank gegeben war (hier zeigt sich, dass es sehr wichtig ist, auch die Sicherheitsaspekte rund um die Datenbank im Auge zu behalten!), konnte das Plugin deaktiviert werden.

Vorgehen zum Entfernen eines WordPress Plugins per Datenbank

  1. Mit phpmyadmin einen Login auf die Datenbank herstellen und einen Sicherheits-Dump der gesamten Datenbank erstellen
  2. Die Tabelle „wp_options“ öffnen und die Zeile mit dem `option_name` „active_plugins“ suchen (wenn die Datenbank einen anderen Suffix hat, die Tabelle mit der Endung „_options“ suchen):
    SELECT * FROM `wp_options` WHERE `option_name` = ‚active_plugins‘
  3. Den Inhalt von `option_value` in ein Textpad oder Notepad kopieren (2x; einmal zur Sicherung, einmal zur Manipulation)
  4. Den String wie untenstehend manipulieren (wenn einzelne Plugins deaktiviert werden sollen). Um alle Plugins zu deaktivieren, kann der Inhalt in ‚option_value‘ einfach geleert werden.
  5. Den manipulierten String wieder zurückschreiben

Bei dem Textwert im Feld `option_value` handelt es sich um Serialize-Array in PHP http://php.net/manual/de/function.serialize.php

Etwas leserlicher dargestellt sieht der Wert so aus:

a:5:
{
i:0;s:41:"better-wp-security/better-wp-security.php";
i:1;s:33:"limit-attempts/limit-attempts.php";
i:2;s:23:"ssclassic/functions.php";
i:3;s:34:"wp-clone-by-wp-academy/wpclone.php";
i:4;s:31:"wp-permalauts/wp-permalauts.php";
}

Da ich im Versuch auch das Plugin “Better-WP-Security“ erst einmal deaktivieren wollte, habe ich beide Plugins erst einmal deaktiviert. Folgende Veränderungen wurden vorgenommen:

  1. „a:5:“ wurde zu „a:3“ -> es sind nur noch drei „Zeilen“ = Plugins im Array
  2. Die Zeilen „i:0“ und „i:1“ habe ich gelöscht (es verbleiben drei Zeilen)
  3. Die Werte der nachfolgenden Zeilen „i:X“ wurden angepasst
  4. Dann wurde der String wieder in eine Zeile gekürzt (Returns gelöscht)
a:3:
{
i:0;s:23:"ssclassic/functions.php";
i:1;s:34:"wp-clone-by-wp-academy/wpclone.php";
i:2;s:31:"wp-permalauts/wp-permalauts.php";
}

Mit dem Neuaufruf der WP-Login-Maske konnte ich mich wieder als Admin anmelden. Beide herausgenommenen Plugins waren nun im Dashboard als „deaktiviert“ ersichtlich. Das Plugin „WP-Better-Sercurity“ habe ich wieder aktiviert. Das Plugin „Limit Attempts“ habe ich gelöscht und durch das Plugin „Limit Login Attempts“ ersetzt.

MySQL Fehler: Incorrect key file for table ‚./[TABELLENNAME].MYI‘; try to repair it

In den vergangenen Wochen ist mir dieser Fehler mehrfach bei einer sehr großen MySQL-Installation aufgetaucht. Es hat einige Zeit gedauert, bis ich diesen Fehler ausgemerzt habe, zumal die betreffende Tabelle über 20 Mio Einträge bei einer Tabellengröße über 14 GB besitzt. Da ist jeder Befehl, jeder Versuch, die Tabelle zu reparieren, nicht in einer Kaffeepause abgetan. Ein Repair dauert mehrere Stunden …

Da es sich um eine MyISAM-Tabelle handelt, ist der erste Versuch natürlich, die Tabelle per „myisamchk“ zu reparieren. Den genauen Ablauf habe ich im Artikel „MyISAM-Datenbank-Tabelle reparieren“ bereits ausführlich beschrieben. Je nach Tabellengröße ist es an der Zeit, in eine ausgedehnte Mittagspause oder gar ins Bett zu gehen. Dieser Befehl dauert seine Zeit und in meinem Fall hat die Reparatur zwar die Tabelle wieder lesbar gemacht, das defekte Key-File jedoch nicht repariert.

Methode 1: Das Keyfile löschen

1-myisamchkIn diversen Foren bin ich über die nachfolgend beschriebene Methode gestoßen, um das Keyfile wieder erneut zu stellen. Um es vorweg zu nehmen: Die Methode hat bei meiner Tabelle nicht funktioniert. Ich kann nicht behaupten, dass die Methode nie funktioniert. Aus diesem Grund beschreibe ich sie hier auch. Nur in meinem Fall führte sie nicht zum Erfolg.
Ziel der Methode ist, dass betreffende Keyfile zu löschen und neu erstellen zu lassen:

  1. Den MySQL-Server stoppen („sudo service mysql stop“)
  2. Das betreffende .myi-File im Dateisystem umbenennen (beispielsweise „name.old“)
  3. Den MySQL-Server wieder starten („sudo service mysql start“)
  4. Nun die Tabelle reparieren lassen ( http://dev.mysql.com/doc/refman/5.1/en/repair-table.html ). Hierbei soll das Keyfile neu erstellt werden, da es ja nicht mehr vorhanden ist.

In meinem Fall brach die Reparatur mit wilden Fehlermeldungen ab. Ich habe danach wieder das Keyfile der Tabelle hinzugefügt und die Tabelle wieder neu repariert („myisamchk“). Dieser Versuch hat mich dann insgesamt über 12 Stunden beschäftigt. In dieser Zeit war die Datenbank also offline.

Methode 2: Tabelle per Dump neu aufbauen

2-mysql-dumpBei dieser Methode wird von der betreffenden Tabelle ein Dump erzeugt. Dies geht relativ schnell mittels „mysqldump –h localhost –u USER –pPASSWORD DATENBANKNAME TABELLENNAME > mydump.sql“ Wir erzeugen also einen Dump der Tabelle mit dem defekten Keyfile. In meinem Fall einfach auf der lokalen Platte (sofern natürlich ausreichend Platz vorhanden ist). Zu beachten ist ebenfalls, dass die Tabelle nicht als defekt gekennzeichnet sein darf („marked as crashed“). Wenn dies der Fall ist, muss zuvor per myisamchk die Tabelle repariert werden (siehe oben).
Nun haben wir also auf der lokalen Platte den Dump „mydump.sql“ liegen. Dann wird es spannend: Wir löschen über mysql die Tabelle mit dem defekten Keyfile. Dann spielen wir den Dump wieder zurück. Dies geschieht mit dem Befehl „mysql –h localhost -u USER –pPASSWORD ZIELDATENBANK < dump.sql“. Wenn nach einem abschließenden Return nicht sofort ein Fehler in der Konsole auftaucht, kann sich unser Puls erst einmal wieder regulieren. Im Filesystem kann gesehen werden, dass die Tabelle angelegt wurde und die Größe der Tabelle stetig wächst. Irgendwann wird die Tabelle ihre vorherige Größe haben und das Keyfile wird neu erstellt. Dann meldet sich die Konsole (hoffentlich) ohne Fehlermeldung wieder zurück.
Diese Methode hat für mich großen Charme, denn einerseits hat der eigentliche Dump und das Zurückspielen – vergleichsweise – wenig Zeit gekostet, zum Anderen habe ich mir ein „Optimize Table“ gespart. Die Tabelle wurde ja durch das Rückspielen der Daten von jeglicher Fragmentierung befreit.

Bilder mit GIMP verkleinern (Speicherplatz optimieren)

Wenn man Bilder im Internet bereitstellen möchte, ist es von Vorteil, wenn die Bilder so klein als möglich sind. Gerade in Hinsicht auf mobile Geräte und Google Page Speed sollte man dieses Thema nicht unterschätzen (siehe auch CSS-Sprites). Wie man Bild unter der freien Grafiksoftware optimal für das Web speichert, beschreibe ich in diesem Artikel.

Ich setze voraus dass dem Leser klar ist, dass man Bilder auf einer Webseite nur so groß einbindet, wie die Größe auch wirklich gebraucht wird. Ein 200 Pixel breites Bild auf 100 Pixel in HTML zu verkleinern ist eine Ressourcenverschwendung, da viel zu unnötige Daten an den Webserver übertragen werden. Auch Google Page Speed moniert dieses Verhalten.
Es sollte auch klar sein, dass ein Bild bei einer Monitoransicht lediglich 72 dpi (dots per inch) benötigt. Wer Rücksicht auf Retina-Displays nehmen will oder muss, sollte sich hierzu weitere Gedanken machen, aber normale Leser nicht mit übergroßen Bildern „belästigen“.
Dann sollte klar sein, dass Bilder für das Web entweder im jpg oder png-Format gespeichert werden sollen. Auf die Unterschiede möchte ich hier nicht weiter eingehen. Lediglich den Hinweis, dass es kein Patentrezept gibt, welches Format „immer besser“ ist. Als Faustregel kann gelten, dass Fotos besser im JPG-, Grafiken im PNG-Format gespeichert werden sollen. Entscheidung: Beides ausprobieren und Ergebnis mit der Dateigröße abwägen.

Bilder mit Gimp verkleinern und schärfen

GIMP Plugin refocus
Das Plugin Refocus bettet sich unter Filter -> Verbessern ein. Es bietet zahlreiche Einstellmöglichkeiten.

Im ersten Schritt sollen die Bilder auf die richtige Zielgröße „zurechtgeschrumpft“ werden. Schon hier gilt es einiges zu beachten. In GIMP wird unter BILD -> BILD SKALIEREN ein Dialog angezeigt, der die Verkleinerung des Bildes zulässt. Auch wenn man meinen könnte, dass die Bilder in der Qualität steigern, wenn man sie verkleinern, ist dies leider ein Trugschluss. Bilder leiden in der Qualität beim Vergrößern (was logisch ist), wie auch beim verkleinern, da hier zum Teil Details wegfallen. Gimp bietet hier die Auswahl „Qualität“ -> „Interpolation“ an. Zwei Filter („Kubisch“ und „Sinc (Lanczos3)“) sollten beim Verkleinern ausprobiert werden. Das beste Ergebnis entscheidet die Filterauswahl.
Nach dem Verkleinern sollte das Bild nachgeschärft werden. Unter FILTER -> VERBESSERN -> SCHÄRFEN kann man den Gimp-Standard-Filter nutzen. Leider ist die Qualität oft nicht ausreichend.
Tipp: Probiert „FILTER -> VERBESSERN -> UNSCHARF MASKIEREN“ aus. Klingt nicht nach schärfen, bringt aber deutlich bessere Ergebnisse. Meist genügen Radius-Werte von 0,9 und Menge 0,20. Einfach einmal ausprobieren.
Tipp „Refocus Plugin“: Ein nachladbares Gimp-Plugin ist „Refocus“. Folgt dem Link für eine weitere Beschreibung. Wer den Plugin-Ordner in GIMP sucht – am Ende des Artikels folgt auch hierzu ein Tipp.

Bild Dateigröße mit GIMP verkleinern

Wenn wir das Bild in der richtigen Größe haben, kann man das Bild einfach als JPG oder PNG speichern und gut – wenn es nur so einfach wäre. Wenn wir nicht das letzte Bit herausquetschen müssen, können wir JPG-Bilder mit einer Rate von 90%, PNG-Bilder mit einer Kompressionsrate von 7 speichern. Kleiner werden JPG-Dateien bei einer kleineren, PNG mit einer höheren Rate. In jedem Fall leidet aber die Qualität.
Deutlich keiner werden die Bilder, wenn man die Farben reduziert. Unter BILD -> MODUS -> Indiziert kann man die Bildfarben mit der Auswahl „Optimale Palette erzeugen“ auf maximal 256 Farben reduzieren. Die Bilder werden deutlich keiner. Noch kleiner werden die Bilder mit der „Internet-optimierten Palette“. Aber Achtung: Fotos werden hiermit schnell unansehnlich. Gerade Farbverläufe leiden hier ganz extrem.
Tipp: Die Rasterung ausprobieren! Der Filter „Floyd-Steinberg (reduzierte Farbbluten)“ liefert oft ein noch an „ansehnliches“ Ergebnis.

Ausprobieren oder eine automatisierte Lösung? Das Save for Web Plugin!

Wie wir sehen, gibt es leider keine „Ich drücke eine Button“-Lösung. Es gibt Anhaltspunkte und Richtlinien, doch wer das letzte Bit bei einer Grafik einsparen möchte, muss viele Möglichkeiten testen. Um einen Eindruck von Kompressionsrate und Zielformat er erhalten, kann das Plugin „Save for Web“ installieren. Dies blendet unter DATEI den Punkt „Save for Web“ ein und lässt die Möglichkeit zu, das Ergebnis einer Kompression in einer Vorschau zu bewerten. Gleichzeitig berechnet das Plugin vor dem Speichern schon die Dateigröße. Somit kann man sich viele Speicherversuche sparen.

Plugin-Ordner einer Gimp-Installation finden

Plugin Ordner von Gimp findenWer ein Script oder ein Plugin in Gimp hinzufügen will, muss lediglich die Datei in das richtige Verzeichnis kopieren. Doch wo ist der richtige Ordner? Gimp liefert darüber freiwillig Auskunft: Unter BEARBEITEN -> EINSTELLUNGEN kann man in der linken Spalte den Eintrag „ORDNER“ (auch in Plus davor) sehen. Hier ist der Pfad zum Plugin- sowie Skript-Ordner ersichtlich.

Abbruch der Putty-Sitzung – Abhilfe dank Screen Tutorial

Verbindung zu externen Linux-Servern stellt man gerne per Putty her. Eine geöffnete Shell in einer Terminalsitzung erlaubt einem die Kommunikation mit dem Server. Doch was passiert, wenn die Verbindung abbricht? Die Kommunikation mit dem Server und alle laufenden Prozesse werden abgebrochen. Das Linux-Tool „Screen“ erlaubt einem, die Verbindung wieder aufzunehmen.

Wie oft habe ich mich darüber geärgert. Eine Tabelle oder der Index einer MySQL-Datenbank ist mal wieder korrupt. Ärgerlich. Doch myisamcheck kann da Abhilfe schaffen. Doch je nach Größe der Datenbank (ich betreue einige MySQL-Datenbanken mit jeweils mehr als 30 Millionen Datensätzen) dauert die Reparatur des MySQL-Index mehrere Stunden. Und selbstredend , dass in dieser Zeit auch einmal die Putty-Verbindung abbricht. Sei es durch die DSL-Zwangstrennung durch den lokalen Provider oder durch ein Zucken im Netzwerk. Das Resultat ist das Gleiche: Das Reparaturprogramm bricht mit dem Disconnect von Putty ab und man muss von vorne beginnen. Und wäre es nicht toll, auch von unterwegs über einen Laptop via UMTS eine Konsolenverbindung aufzubauen und diese im Büro oder zu Hause weiter zu nutzen? Hierfür gibt es das Tool „Screen“, welches unter Ubuntu bereits integriert ist.
In den meisten Linux-Distributionen kann man Screen wie folgt installieren:

sudo apt-get install screen

Tutorial: Benutzung von Screen

screen tutorial start
Eine abgebrochene Putty Session beendet auch einen laufenden Job. Das Tool Screen kann Abhilfe schaffen.

Mit dem Befehl „screen“ in der Konsole startet man Screen. Drückt eine weitere Taste und – nichts geschieht. Könnte man meinen. Doch in Wirklichkeit habt Ihr nun die Möglichkeit, verschiedene Screenfenster zu erstellen und auf diese wieder zuzugreifen. Nun erstellen wir ein neues Screen-Fenster. Wir drücken „Ctrl-a“ + „c“ (siehe „Tastenkombinationen von Screen“). Wir erstellen damit ein neues Fenster. Damit in dem Fenster auch etwas „passiert“, geben wir „top“ ein und schauen uns ein wenig die Prozesse an.

screen-fenster-erstellen
Zum Testen von Screen einfach einmal mehrere Session starten

Nun drücken wir wieder „Ctrl-a“ + „c“ und erhalten einen neuen Screen. Hier geben wir beispielsweise „ls“ ein und werfen einen Blick ins Filesystem. Um jetzt zwischen den Screens zu wechseln, gibt es mehrere Möglichkeiten. Zuerst können wir die Screens über die Nummer (0,1,2 …) ansprechen. Mittels „Ctrl-a“ und dem Druck von „0“ erhalten wir das erste Fenster. Mit „n“ das nächste (next) und mit „p“ (previous) das vorherige Fenster. Ein nicht mehr benötigtes Fenster kann man mit „k“ wie „kill“ schließen.

Wiederaufnahme einer Putty-Verbindung

Wenn eine Session via Screen läuft und Putty die Verbindung verliert, kann man mit „screen –ls“ die aktiven Sessions einsehen
[root@MyServer root]# screen -ls
There are screens on:
1599.tty1.MyServer (Detached)
1 Socket in /var/run/screenens/S-root.

Mittels dem nachfolgenden Befehl (Name der Session anpassen!) kann die Session neu verbunden werden:

[root@ MyServer root]#screen -r 1599.tty1.MyServer

Wenn nur eine Session vorhanden ist, genügt auch:
screen -r (reattach)

Tastenkombinationen von Screen

screen-tutorial-ctrl-a
CTRL und die Taste A oder STRG und die Taste A ist die Basis zur Kommunikation mit Screen.

Ctrl-a: Diese erste Tastenkombination ist sozusagen der „Schlüssel“ zu Screen. Drückt die Taste „Ctrl“ oder „strg“ und die Taste „a“ gleichzeitig. Dann folgt einer der nachfolgenden Tasten:

  • „Ctrl-a“ + „c“: Neues Fenster (create) erstellen
  • „Ctrl-a“ + „n“: Sprung zum nächsten (next) Fenster
  • „Ctrl-a“ + „p“: Sprung vom vorherigen (previous) Fenster
  • „Ctrl-a“ + [0,1,2 …] : Durch die Auswahl der Nummer des Screens (der erste Screen startet bei „0“) kann ein Fenster gezielt angesprungen werden
  • „Ctrl-a“ + „K“: Ein nicht mehr benötigter Screen kann mit „K“ (Kill) geschlossen werden. Screen frägt nach, ob das Fenster wirklich geschlossen werden soll („y“ (yes))

Weitere Links:
Screen FAQ

CSS Sprites erstellen

Kundenprojekt: Tabelle, 40 Produkte die in 40 Zeilen der Tabelle untergebracht wurden und dazu 40 Vorschaubilder. Die Webseite ist fertig, der Kunde zufrieden, und der Browser arbeitet klaglos beim Öffnen der Webseite 40 Zugriffe zum jeweiligen Image ab. Dank schnellem Server und guter Internetanbindung ist die Seite verhältnismäßig schnell aufgebaut. Doch ist dies der richtige Weg? Und was sagt Google Page Speed?

Was sind Sprites?

sprite beispiel
Ein CSS Sprite ist eine einzige Grafik, die viele verschiedene Grafiken enthält. Diese Sammlung wird nur einmal geladen – jede einzelne Grafik steht dann zur Anzeige bereit.

Wer beim Begriff „Sprite“ nur an mit Zucker versetztes Wasser denkt, hat Nachholbedarf. Wem beim Begriff „Sprite“ der Commodore C64 mit seinen Sprite-Grafiken in den Sinn kommt, ist schon ein wenig näher am Thema.  Doch mit den „Geister-Grafiken“, die damals die Spielewelt revolutionierten, hat das CSS-Sprite wenig zu tun. Unter einem CSS-Sprites versteht man eine einziges Grafikelement, das viele einzelne Grafiken enthält; also eine Art Grafiksammlung.

Warum ist der Einsatz von CSS-Sprites sinnvoll?

Wie oben beschrieben hatte ich in einem Kundenprojekt 40 einzelne, jeweils 70x70px große Bilder, die alle auf einer Seite angezeigt werden sollten. Dies bedeutet, dass der Browser beim Aufruf der Seite mindestens 41 Requests, also Zugriffe auf den Quellserver, sendete. Der Webserver wiederum musste 41 einzelne Dateien zum aufrufenden Kunden senden. Dies bedeutet zum einen Last für den Server, aber auch Wartezeit für den aufrufenden Kunden, da ein Browser nur eine begrenzte Anzahl von parallelen Anfragen verarbeiten kann. In meinem Praxisbeispiel habe ich die 40 kleinen Images in zwei Grafiken zu je 20 Teilgrafiken zusammengefasst. Nun hat der Browser nur noch drei Requests (also die eigentliche Seite sowie zwei Sprite-Grafiken) nachzuladen.

Sinkt auch die Dateigröße bei CSS-Sprites?

Nach meiner Erfahrung eher: Nein. Zumeist ist das CSS-Sprite ungefähr so groß wie die Summe der Einzelgrafiken. Wenn viele unterschiedlichen Grafiken (Stil und Farbe) zusammengefasst werden, kann die Dateigröße der gesamten Grafik sogar deutlich größer sein als die Summe der Einzelgrafiken, da eine Reduzierung der Farbpallette bei unterschiedlichen Grafiken in einem Gesamtimage nur schwer erreicht werden kann. Hier kann es sinnvoll sein, Grafiken mit gleicher Farbpallette zu gruppieren.
Beim Einsatz von Sprites geht es nicht um eine weitere Reduktion der Größe der nachzuladenden Grafiken, sondern um die Reduzierung der Serveranfragen. Dies rechtfertigt sogar eine Erhöhung der Imagegröße in der Summe oder das Laden von vielleicht nie anzuzeigenden Images (Hover Effekt)

CSS Sprite erstellen

mit gimp einfach ein sprite erstellen
Mittels Generator kann ein Sprite einfach erstellt werden. Aber auch manuell kann ein Sprite erstellt werden. Hier wird mittels GIMP eine Grafiksammlung generiert. Hilfslinien helfen beim Zusammenbau des Sprites.

Ein CSS Sprite zu erstellen ist kein Hexenwerk. Aber es ist mühselig. Vor allem, wenn die Grafiken in unterschiedlichen Größen vorliegen. Sind es nur eine Handvoll Grafiken jeweils in der gleichen Größe, können die Bilder einfach nacheinander oder untereinander mittels eines Grafikprogrammes wie beispielweise GIMP „zusammengeklebt“ werden.
Im Netz werden zahlreiche Generatoren angeboten, die das Sprite und sogar noch den dazugehörenden CSS-Code erstellen.

CSS-Sprite Beispiele

Beispiel 1: Transparentes Bild
Die folgende Möglichkeit, ein CSS-Sprite anzuzeigen ist recht einfach, zeigt aber eine Schwäche: Es wird ein IMG-Tag verwendet, der zu einem transparenten Pixel linkt. Dies bedeutet einen Request mehr (nicht pro Bild, da der Browser diese Abfrage nur einmal durchführt), also das Image cached. Mir ist in der Praxis jedoch aufgefallen, dass das Sprite-Image jedoch auf mobilen Browsern nicht immer angezeigt wird.
CSS:

img.mysprite {
/* Die Größe des angezeigten Image und Pfadangabe definieren */
	width: 70px;
	height: 70px;
	background-image: url(Pfad_zum_Sprite);
}

/* Das einzelne Image wird positioniert – Ausschnitt aus der Grafiktabelle */
img.sprite1 {
	background-position: 0 0;
}

img.sprite2 {
	background-position: -70px 0;
}

Aufruf im HTML-Code

<img class="mysprite sprite1"  src="img_trans.gif" width="1" height="1" />

Zu beachten ist, dass das „img_trans.gif“ auch wirklich vorhanden sein muss, da sonst der Browser ein fehlerhaftes Bild einblendet. Das transparente Gif wird einfach einmal mit Gimp angelegt (1×1 Pixel, transparent).

Beispiel 2: Hyperlink
Wenn dem Image ein Link mitgegeben werden soll, ist diese Möglichkeit von Vorteil. Allerdings kann der Link auch – wie hier – auf die eigene Seite zeigen.

.sprite {
	clear: both;
	display: block;
	margin-left: auto;
	margin-right: auto;
	width: 70px;
	height: 70px;
        background: url(Pfad_zum_Image);
}

.wss1 {
    background-position: 0 0;
}

.wss2 {
    background-position: -70px 0;
}

HTML-Aufruf

<a class="sprite wss1" href="#"></a>

Beispiel 3: Wechselndes Sprite Image beim „Überfahren“ des Image mit der Maus
Bilder, die sich ändern, wenn sie mit der Maus „überfahren“ werden, sind schon recht alt, aber immer wieder schön anzusehen. Eventuell ändert sich die Hintergrundfarbe, das Bild verschiebt oder vergrößert sich – all das ist möglich. Bisher hat man solche Effekte per JavaScript via MouseOver generiert. Dank CSS-Sprite wird einfach per Hover ein anderer Teil des Sprite angezeigt:

CSS

#mysprite {
width: 70px;
height: 70px;
text-decoration: none;
display: block;
background: url(PFAD_zum_IMAGE.png) 0 0;
}

# mysprite:hover, # mysprite:active {
background-position: 70px 0;
}

HTML:

<a id="mysprite" href="#"></a>

 

Batch: Anzahl Dateien im Ordner und löschen

Auf die Problematik Dateien unter Windows zu löschen, die älter als X-Tage sind, bin ich bereits im Artikel („Dateien automatisiert löschen, die älter als x-Tage/Datum sind“) eingegangen. Darauf basierend stellt sich folgendes Problem: Ein Sicherungsskript erstellt jeden Tag ein neues Backup, welches auf einer NAS mit Windows-Zugriff gespeichert wird. Im entsprechenden Sicherungsverzeichnis laufen nun jeden Tag Dateien auf und das Verzeichnis belegt von Tag zu Tag mehr Speicherplatz. Also sollen alle Sicherungen, die beispielsweise älter als 5 Tage sind, automatisch gelöscht werden. Dies ist die Ausgangssituation des oben beschriebenen Skriptes.
In der Praxis kommt es leider vor, dass die Sicherung ausfällt. Ein Tag ist zu verschmerzen. Wenn die Sicherung jedoch dauerhaft ausfällt, ist nach wenigen Tagen auch jedes Sicherungsfile gelöscht. Und da Murphy des EDV-lers bester Freund ist, wird nach dem Löschen des letzten Backups auch ein Ausfall des Quell-Systems eintreten.

Also geht es nun darum, das Skript so zu optimieren, dass es Dateien nur löscht, wenn mindestens X (=2,3 (…) 10) Dateien im Ordner noch vorhanden sind. Fallen die Sicherungen aus, verbleiben immerhin die Sicherungen im Sicherungsordner und werden nicht gelöscht.

Optimal wäre in diesem Fall noch, wenn der Administrator benachrichtigt werden würde, wenn ein solcher Fall eintritt. Aber auch dies ist möglich. In der Zeile „IF „%countFiles%“ GTR „%minFiles%“ (“ beginnt der IF-Block, in dem der Code Platz findet, der die Files löscht. Es sind also genug Dateien vorhanden. Im folgenden „ELSE“-Block gibt das Skript lediglich ein Echo („ echo Zuwenig Dateien zum Löschen!“) aus, was wenig sinnvoll ist. Hier könnte das Skript um die Möglichkeit erweitert werden, dem Admin eine Nachricht zukommen zu lassen, sich die Sicherungen einmal anzuschauen.

Im Skript selbst sind nur zwei Variablen vor dem Einsatz anzupassen. Die Variable „minFiles“ muss mit der Mindestanzahl der Dateien gefüllt werden, die auf jeden Fall im Zielverzeichnis verbleiben sollen. Und der Ordner bzw. das Zielverzeichnis selbst muss deklariert werden. Dies geschieht in der Variable „path“.

Das Skript kann einfach als Batch über Scheduled Task aufgerufen werden.

Batch: Wenn Befehle aus Windowssystem32 nicht gefunden werden
Eine Falle, in welche ich mal wieder getappt bin. In einem Batch-Skript gibt es Probleme mit dem Aufruf von Befehlen wie forefiles, net use oder ähnlichen. Diese Befehle liegen alle im System32-Ordner, doch der Aufruf wird mit einem Fehler quittiert (Aufruf fehlerhaft oder er kann Datei nicht finden). In einem solchen Fall sollte man zuerst einen Blick auf seine gesetzten Variablen innerhalb des Skripts werfen. Ist dort ein „SET Path=“ vorhanden, hat man (versehentlich) den System-Pfad überschrieben. Nun versucht das Skript die Befehle in dem neu gesetzten Pfad zu finden, was sicherlich schiefläuft.

Linux: Für die Freunde von Linux und der Bash gibt es hier eine Anleitung, wie man etwas ähnliches unter Linux bewerkstelligen kann.

@echo off
REM **************************************************
REM *** Skript löscht Dateien aus einem Ordern, die älter als x Tage sind.
REM *** https://sirmark.de/computer/dateien-automatisiert-loeschen-die-aelter-als-x-tagedatum-sind-191.html
REM ***
REM *** Anpassung 2013: Es wird zuerst geprüft, 
REM *** ob noch mindestens X (minFiles) Dateien vorhanden sind.
REM *** Wenn Mindestanzahl an Dateien NICHT vorhanden sind
REM *** (beispielsweise weil ein Sync/Upload nicht mehr funktioniert),
REM *** löscht das Skript KEINE Daten mehr.
REM 
REM ### VARIABLEN ###
REM minFiles = 5 : Anzahl an Dateien, die mindestens im Verzeichnis liegen müssen
REM Pfad=C: (...) : Pfad zum Ordner, der überwacht werden soll
REM     -> Lokaler Pfad (cmd liegt im Verzeichnis) : Pfad=%~1
REM AlterTage: (min) Tage, wie alt die zu löschenden Dateien sein sollen 
REM Laufwerksbuchstabe: Da Forfiles keine UNC-Pfade beherrscht, wird dieser Laufwerksbuchstabe gemappt
REM countFiles : Interne Zählvariable
REM **************************************************

setlocal
SET minFiles=5 

REM Forefiles kann NICHT mit UNC-Pfaden umgehen
REM Hier wird ein Laufwerksbuchstabe aus dem UNC-Pfad mit NET USE gemappt
REM und nach dem FORFILES Befehl wieder gelöscht
SET Pfad=\\192.168.0.x\meine\Ordnerstruktur
SET Laufwerksbuchstabe=J
SET AlterTage=5
SET countFiles=0

REM Prüfe, wieviel Dateien im Ordner vorhanden sind
if (%Pfad%)==() set Pfad=%~dp0
set first=%Pfad:~0,1%
set last=%Pfad:~-1%
if not (%first%)==(^") set Pfad=^"%Pfad%
if not (%last%)==(^") set Pfad=%Pfad%"
for /F %%i in ('dir %Pfad% /B /A-d') do set /A countFiles=countFiles+1

REM Ausgabe der Daten 
echo %countFiles% Dateien in Pfad - Min: %minFiles%

REM Prüfung auf Mindestanzahl an Dateien
if %countFiles% GTR %minFiles% (
    REM Genug Files vorhanden, lösch die Dateien
                echo Mappe das Laufwerk ...    
                NET USE %Laufwerksbuchstabe%: %Pfad% /PERSISTENT:NO

                echo Loesche ...
                REM Achtung: Unterschiedliche Versionen von Forfiles -p != /P -> genaue Syntax lokal mit /? ermitteln!
                REM Test - Files nur anzeigen: forfiles -p%Pfad% -d-%AlterTage% -c"CMD /C echo @FILE"

                REM UNSCHARF: Zeigt nur die Files an. Zum Löschen das folgende echo in del ändern: "CMD /C del @FILE"
                forfiles /P %Laufwerksbuchstabe%: /D -%AlterTage% /C "CMD /C echo @FILE"          

                echo Gebe den Laufwerksbuchstaben wieder frei ...
                net use %Laufwerksbuchstabe%: /D /Y
) ELSE (
    echo Zuwenig Dateien zum Löschen!
)
endlocal

Rumble in the Jungle: Abwehr von Referer Spam

Referer Spam (oder auch „Referrer Spam“) ist schon sehr alt und eigentlich harmlos. Dennoch ärgert mich diese Spam-Art, zumal sie gerade in den letzten Monaten bei einigen Projekten, die ich betreue, vermehrt auftritt. Nachdem ich wiederum einige Wochen tatenlos zugeschaut habe, leitete ich nun Gegenmaßnahmen ein. Erfolgreich. Doch es ist ein Kampf gegen Windmühlen.

Was ist Referer Spam?

Wenn ein Benutzer einen Link auf einer Webseite klickt, wird bei der Weiterleitung die Zielseite als sogenannter Referer mit übertragen. Im Logfile des Zielservers taucht also die Quell-Seite mit auf. Somit kann der Administrator nachvollziehen, woher der Klick kam.

Was bezwecken Spammer mit Referer Spam?

Referer Spam im Webalizer Log
Monatsanfang und immer mehr .ru-Webseiten tauchen in den Logfiles auf. Es ist davon auszugehen, dass die Spammer zentrale Listen pflegen.

Referer-Spam ist Werbung. Nichts weiteres. Der Administrator prüft die Logfiles und jeder Seiteninhaber, der ein wenig Suchmaschinenoptimierung (SEO) betreibt, interessiert sich auch für die Links (Backlinks), die auf seine Seite zeigen. Es taucht eine neue Backlinksquelle in den Logs auf? „Prima!“, denkt sich der Seiteninhaber und schaut sich die Quellseite an. Enttäuscht wird er feststellen, dass die Seite keinen Link zu seiner Seite beinhaltet, sie aus SEO-Sicht also wertlos ist. Oftmals wird er aber auch erleichtert feststellen, dass kein Link vorhanden ist – denn welcher SEO will gerne Links von russischen Erwachsenenangebote, dubiosen Pillenverkäufer- oder Casinoseiten? Dennoch hat der Spammer zumindest ein Ziel erreicht: Er hat es geschafft, dass ein Besucher seine Seite besucht hat. Durch den Referer hat er quasi seine Visitenkarte in den Logfiles hinterlassen und der Admin ist der Einladung gefolgt. Neudeutsch würde man dies „Direktmarketing“ nennen.

Früher war auch ein Skript auf vielen Webseiten anzutreffen, welches die letzten Referers auswertete und die Quellseite samt Backlink anzeigte. Sozusagen eine Rückwärtsverlinkung. Hier macht der Referer-Spam wirklich Sinn, denn durch das Skript erhält der Spammer automatisiert einen öffentlich sichtbaren Backlink.
Dies ist auch der Fall, wenn ein Analysewerkzeug wie AWStats oder Webalyzer öffentlich zugänglich ist (siehe „Ist Referer-Spam schädlich“).

Wie kann man Referer-Spam betreiben?

Prinzipiell ist dies sehr einfach. Man schreibe ein kleines Skript, welches den ganzen Tag eine bestimmte Liste an Webseiten aufruft. Es muss sichergestellt sein, dass das Skipt beim Aufruf der fremden Seite den Referer auf die eigene Seite überträgt – besser gesagt:  den Referer fälscht, denn der Aufruf kommt ja nicht wirklich über diese Seite.

Aufwand: Ein alter Rechner, eine DSL-Leitung mit täglich wechselnden IPs und ein Skript, welches man in einer ersten Version sicherlich innerhalb einer Stunde fertig programmiert hat. Also: Kein wirklicher Aufwand.

Ist Referer-Spam schädlich?

Eigentlich nicht, sofern man die Referers nicht auf der eigenen Seite ausgibt. Wenn ja, entsteht ein Backlink zu der Spammer-Seite, welches aus SEO-Sicht schädlich kein kann. Zu beachten ist, dass man – schon aus vielen anderen Gründen – den Zugriff auf Analysedaten von Webalizer und AWStats unterbinden sollte (Passwortschutz), da sonst auch hier ein Backlink entsteht.

Wenn man diese zwei Dinge beachtet, ist Referer-Spam nicht weiter schädlich. Zu Recht die Frage, warum ich trotzdem gegen den Spam vorgehe. Nun, zum einen verbraucht der Spam meine Bandbreite. Auch kein wirkliches Problem, wenn der Spammer meine Seite nur einmal am Tag aufruft. Dann ist dies zu vernachlässigen. Läuft sein Skript jedoch Amok und ruft meine Seite beispiesweise sekündlich auf, müssen Abwehrmechanismen getroffen werden.

Das – für mich hauptsächlich – größte Ärgernis ist, dass meine Statistiken verfälscht werden.  Ich prüfe regelmäßig die Referers und die Spammer nehmen hier gerne Top-Platzierungen ein, denn sie besuchen meine Seiten täglich – zum Teil mehrmals. Auch die Anzahl der Besucher wird so verfälscht.

Eine rechtliche Betrachtung erspare ich mir. Geneigte Leser können im Law-Blog weitere Informationen finden. Die Zeit wäre mir zu kostbar.

Tür zu: Abwehr von Referer-Spam

Was also bleibt ist die Möglichkeit, dem Spammer keinen Zutritt zum eigenen Webserver zu gewähren. Die nachfolgenden Beispiele sind Regeln, die in der .htaccess-Datei (oder vhosts) Platz finden.

Den Gedanken, die IP eines Spammers zu blockieren, kann man schnell fallenlassen, denn die Zugriffe erfolgen meist über wechselnde IPs.  Dennoch der Vollständigkeit halber nachfolgend die Möglichkeit, eine bestimmte IP zu sperren:

order allow,deny
deny from xxx.xxx.xxx.xxx
allow from all

Im obigen Beispiel ist „xxx.xxx.xxx.xxx“ durch die aufrufende IP zu ersetzen. Wenn er Spammer wechselnde IPs nutzt, wird man mit dieser Methode nicht glücklich, da man jedes Mal die IP anpassen muss. Schnelle Abhilfe kann dieser Eintrag in der .htaccess-Datei jedoch schaffen, wenn kurzzeitig von einer IP (oder einer Handvoll IPs) vermehrt (oder gar eine kritische Masse) Zugriffe kommen und man diese Aussperren möchte.

Eine Möglichkeit ist, mittels der RewriteCond Signaturen der unerwünschten Referer aufzugreifen und zu blockieren. Zu beachten ist jedoch, dass man hier sehr schnell mehr Referers aussperrt, als man ursprünglich beabsichtigt. Denkbar ist folgende RewriteCond, die Aufrufe mit „casino“ oder „.ru“ blockiert:

RewriteEngine on
RewriteCond %{http_REFERER} casino [OR]
RewriteCond %{http_REFERER} .ru
RewriteRule .* - [forbitten, last]

Hinterlässt der Spammer eine eindeutige Signatur, kann mittels „SetEnvIfNoCase“ der User-Agent gesperrt werden. Diese Methode eignet sich auch hervorragend um „news aggregator sites“, die den eigenen Text-Content oder Bilder hotlinken, vor die Schranken zu verweisen. Vor einiger Zeit war „IzyNews“ ein solcher Kandidat, der in den Logfiles folgende Signatur hinterlassen hat:

„IzyNews/1.0 (http://izynews.com, multiple subscribers)“

Das folgende Beispiel erzeugt eine Umgebungsvariable (hier: “leecher“), wenn der Agent mit seiner Signatur zugreift. Die deny-Regel verwehrt dem Agent dann den Zugriff; er erhält einen „Error 403“.

SetEnvIfNoCase User-Agent „IzyNews/1.0“ leecher=yes
order deny,allow
deny from env=leecher

Im Falle von IzyNews.de erfolgte der Content-Klau per RSS, die Bilder wurden vom Originalserver nachgeladen. Somit entging einem der Leser. Geblieben ist dem Seitenbetreiber nur der Traffic für das Ausliefern der Bilder. In einem solchen Fall greift natürlich Signatur-Regel nicht, denn der Benutzer einer solchen News Aggregator Site sendet bei seinem Request den User-Agent seines eigenen Browsers. Referenziert wird der Zugriff jedoch über den Originalserver, so dass die Regel einfach über die nachfolgende Zeile erweitert werden kann:

SetEnvIfNoCase Referer izynews.de leecher=yes

So kann also wie im nachfolgenden Beispiel eine Bad-Word-Liste in der htaccess-Datei gepflegt werden, die den Referer-Spam ein wenig einschränkt. Die Pflege ist jedoch auswändig.

SetEnvIfNoCase Referer seite1.ru leecher=yes
SetEnvIfNoCase Referer seite2.ru leecher=yes
order deny,allow
deny from env=leecher

Mit Captcha gegen WordPress Kommentar Spam

Die Kommentarfunktion gehört als elementarer Bestandteil zu WordPress und vielen weiteren Content Management Systemen (CMS). Doch leider ist der Webmaster gezwungen, diese schon bei der Installation zu deaktivieren, da er sonst tagtäglich hunderte, wenn nicht gar tausende Spam-Kommentare erhält. Leider keine Übertreibung. Harter WordPress-Alltag eines WordPress Admins.

klassisches-wordpress-captcha
Kommentar-Spam: Mittels Captcha-Breaker können auch solche Captchas automatisiert überwunden werden.

Auch auf diesem Blog habe ich die Kommentarfunktion zuerst deaktiviert. Doch von Anfang an fand ich es schade, dass Leser keinen Kommentar hinterlassen können. Auf anderen von mir betreuten Blogs habe ich die Kommentarfunktion aktiviert und das Absenden der Kommentare per WordPress Plugin „SI CAPTCHA Anti-Spam“ ermöglicht. Der Erfolg ist mäßig. Es erreichen je nach Blog einem sinnvolle Kommentare, doch das Spam-Aufkommen sinkt nicht in einen akzeptablen Bereich. Ich selbst ging davon aus, dass Captchas durchaus das Aufkommen minimieren (das glaube ich nach wie vor) und war mir sicher, dass ein (ScrapeBox)-Spammer die Kosten für den Captcha-Brecher scheuen. Musste jedoch eines besseren belehrt werden. Vor ein paar Tagen lief wohl einem Spammer sein Skript Amok und hinterließ auf einer von mir betreuten Seite mit diesem WordPress Plugin mal über 1.000 Spam-Kommentare in ein paar Stunden.

Auf sirmark.de habe ich dann die Kommentarfunktion von mood.it getestet. Positiv: Kein einziger Spam-Kommentar im Testzeitraum. Negativ: Kein einziger Kommentar überhaupt. Es zeigt sich also, dass mood.it in Deutschland noch zu wenig bekannt ist und sich jemand für einen sinnvollen Kommentar nicht unbedingt erst noch irgendwo extern registriert. Zudem hat diese Lösung einen ganz entscheidenden Nachteil: Bei den Kommentaren geht es auch darum, dass der Content einer Seite durch neuen Text „aufgefrischt“ und vielleicht sogar noch durch wichtige Keywords ergänzt wird. Diesen Effekt verbaut man sich, wenn man eine externe Lösung einsetzt.

Nach diesen Erfahrungen werde ich seit heute eine neue Lösung hier testen. Das WordPress Plugin heißt einfach „Captcha“, liegt aktuell in der Version 3.7.1 vor und erzeugt kein Text, der abzutippen ist, sondern eine simple Rechenaufgabe. Ich erhoffe mir, auch nach dem Lesen einiger Erfahrungsberichte , dass diverse Übersee-Pillenverkäufer über ein deutsches Text-Captcha stolpern. Ob dies der Fall sein wird – wir werden sehen.

Akzeptanz der Captchas

Oftmals liest man, dass es negativ sei Captchas einzusetzen, da so die Akzeptanz der Kommentarschreiber leidet. Ich persönlich behaupte, dass gerade das Gegenteil eintritt (zumindest bei mir persönlich). Wenn ich auf einer Seite einen Kommentar hinterlasse möchte, bin ich bei einer offenen Kommentarfunktion immer sehr skeptisch. Da ich abschätzen kann, wie viel Spam-Kommentar der Webmaster erhalten dürfte, gehe ich davon aus, dass mein Kommentar in der Masse verschwindet und nie veröffentlicht wird. Ist ein Captcha eingesetzt, deutet dies zumindest darauf hin, dass der Webmaster sich mit der Problematik auseinandergesetzt hat. Die Chance, dass mein Kommentar veröffentlich wird, steigt meines Erachtens.

WordPress: Mit PHP eigenes Widget (Plugin) erstellen

Auch wenn man WordPress seit vielen Jahren nutzt, kommt man nur selten in die Verlegenheit, ein Plugin selbst zu programmieren. Auch wenn es einem oftmals in den Fingern juckt und man kurz davor ist, ein eigenes Widget zu erstellen, zeigt einem eine kurze Suche, dass es bereits eine fertige und kostenlose Lösung gibt. Warum dann also selbst programmieren?
Nach vielen Jahren der WordPress-Nutzung kam ich vor wenigen Tagen zum ersten Mal an eine Stelle, an der ich ein eigenes Widget programmiert habe. Vielleicht hätte nach einer längeren Suche sich eine fertige Lösung ergeben, doch da es – wie immer – mal wieder schnell, schnell gehen musste, griff ich in die Tasten. Im vorliegenden Fall musste ein fertiges Theme genutzt werden, in dessen Footer-Bereich mehrspaltig eine Datenbankabfrage angezeigt werden sollte. Jede Spalte wurde vom Theme mit einem eigenen Widget bedient. Im Kundenfall jedoch sollten in allen drei Spalten nicht unterschiedliche Abfragen, sondern eine Abfrage verteilt auf drei Spalten angezeigt werden.
Wordpress-Plugin-selbst-programmiertDie einfachste wie auch schnellste Lösung wäre sicherlich der Eingriff in das (fremde) Theme gewesen, was aber die Wartbarkeit bzw. das Update des Themes erschwert hätte. So lautete der Auftrag, dies per Widget zu lösen.
Untenstehend zeige ich Euch eine Basisklasse „MyTestWidget“, die als Layout für die meisten Plugins dienen kann. Geht folgendermaßen beim Erstellen vor:

  1. Kopiert den Code in ein Textpad, speichert den Code als [mein-widget-Name].php
  2. Passt im Code die Stellen „MyTest Widget“ und „My_Test_Widget“ entsprechend an
  3. Kopiert den Code in den WordPress-Ordner „/wp-content/plugins“. Im WordPress Backend sollte nun unter „Plugins“ Euer Plugin auftauchen. Wenn Ihr es aktiviert, erscheint auf der Layout/Widgets Seite das Widget, welches Ihr in den entsprechenden Widget-Area ziehen könnt.
  4. widget-einstellungNach diesem kurzen Test müsst Ihr natürlich das Plugin entsprechend anpassen. In der Funktion „public function form($instance)“ werden die Parameter abgehandelt, die im Widget-Bereich vom Admin gesetzt werden sollen. Im Code findet Ihr die Variablen ‚footer-books-title‘ , ‚footer-books-start‘  und ‚footer-books-ende‘ . Passt hier Eure Angaben an. Ein Beispielcode, wie mit einer HTML-Texteingabe umzugehen ist, findet Ihr auskommentiert im Code.
  5. Die Eingaben werden im Plugin auf Richtigkeit geprüft und in der WordPress-Datenbank gespeichert. Diese Stellen sind ebenfalls anzupassen.
  6. Nun wird es endlich spannend. In der Funktion „public function widget($args, $instance)“ erfolgt endlich die Ausgabe des Widgets. Im Code könnt Ihr sehen, wie man auf die Widget-Parameter und die Datenbank zugreift. Der Beispielcode hier macht relativ wenig; er dient nur als Demonstration.
  7. Und das war´s auch schon. Die restlichen Funktionen dienen der Registrierung etc. Weitere Infos findet Ihr wie immer unter wordpress.org
<?php
/**
 * Plugin Name: MyTest Widget
 * Plugin URI: http://sirmark.de
 * Description: Basis-Code für ein WordPress - Widget
 * Version: 1.0
 * Author: sirmark
 * Author URI: http://sirmark.de
 */
if(!class_exists('My_Test_Widget')) {
    /**
     * Class My_Test_Widget
     * Aufbau eines Test Widgets
     */
    class My_Test_Widget extends WP_Widget {
        private $var_sTextdomain; // Variable Textdomain: Übersetzungen des Widgets

        /**
         * Konstruktorfunktion (PHP4).
         *
         * Diese Funktion muss den gleichen Namen haben, wie das Klasse des Widgets.
         * Somit wird diese Funktion direkt nach dem Initialisieren der Klasse
         * aufgerufen und ausgeführt. Da dies der Konstruktor für PHP4 ist wird durch diesen
         * direkt die Konstruktor-Funktion für PHP5 aufgerufen.
         *
         * @see My_Test_Widget::__construct()
         */
        public function My_Test_Widget() {
            My_Test_Widget::__construct();
        } // END function My_Test_Widget()

        /**
         * Konstruktorfunktion (PHP5).
         *
         * @param array $widget_options Optional Passed to wp_register_sidebar_widget()
         *   - description: shown on the configuration page
         *   - classname
         * @param array $control_options Optional Passed to wp_register_widget_control()
         *   - width: required if more than 250px
         *   - height: currently not used but may be needed in the future
         */
        public function __construct() {
            $this->var_sTextdomain = 'my-Test-widget';

            /**
             * Übersetzungsfunktion für das Widget aktivieren.
             * Die Sprachdateien liegen im Ordner "l10n" innerhalb des Widgets.
             */
            if(function_exists('load_plugin_textdomain')) {
                load_plugin_textdomain($this->var_sTextdomain, PLUGINDIR . '/' . dirname(plugin_basename(__FILE__)) . '/l10n', dirname(plugin_basename(__FILE__)) . '/l10n');
            }

            $widget_options = array(
                'classname' => 'My_Test_Widget',
                'description' => __('Kurze Beschreibung des Widgets.', $this->var_sTextdomain)
            );

            $control_options = array();

            $this->WP_Widget('My_Test_Widget', __('My TestWidget', $this->var_sTextdomain), $widget_options, $control_options);
        } // END function __construct()

        /**
         * Widgetformular – dient zur Einstellung des Widgets
         *
         * @see WP_Widget::form()
         */
        public function form($instance) {
            /**
             * Standardwerte
             *
             * @var array
             */
            $instance = wp_parse_args((array) $instance, array(
                'footer-books-title' => '',
                'footer-books-start' => '0',
                'footer-books-ende' => '5'
            ));

            // Titel
            echo '<p style="border-bottom: 1px solid #DFDFDF;"><strong>' . __('Title', $this->var_sTextdomain) . '</strong></p>';
            echo '<p><input id="' . $this->get_field_id('footer-books-title') . '" name="' . $this->get_field_name('footer-books-title') . '" type="text" value="' . $instance['footer-books-title'] . '" /></p>';
            echo '<p style="clear:both;"></p>';

            ?>
            <p>
                <label for="<?php echo $this->get_field_id('footer-books-start'); ?>"><?php echo 'Start:'; ?></label>
                <input id="<?php echo $this->get_field_id('footer-books-start'); ?>" name="<?php echo $this->get_field_name('footer-books-start'); ?>" type="text" value="<?php echo $instance['footer-books-start']; ?>" />
            </p>
            <p>
                <label for="<?php echo $this->get_field_id('footer-books-ende'); ?>"><?php echo 'Anzahl:'; ?></label>
                <input id="<?php echo $this->get_field_id('footer-books-ende'); ?>" name="<?php echo $this->get_field_name('footer-books-ende'); ?>" type="text" value="<?php echo $instance['footer-books-ende']; ?>" />
            </p>            
            <?php

            // Beispielcode für ein Textfeld
           // echo '<p style="border-bottom: 1px solid #DFDFDF;"><strong>' . __('Textarea:', $this->var_sTextdomain) . '</strong></p>';
           // echo '<p><span style="display:inline-block;">' . __('Write some text here ...', $this->var_sTextdomain) . '</span><textarea style="width:100%;" id="' . $this->get_field_id('my-widget-text') . '" rows="10" name="' . $this->get_field_name('my-widget-text') . '">' . $instance['my-widget-text'] . '</textarea></p>';
           // echo '<p style="clear:both;"></p>';
        } // END function form($instance)

        /**
         * Widgeteinstellungen in die Datenbank schreiben.
         *
         * @see WP_Widget::update()
         */
        public function update($new_instance, $old_instance) {
            $instance = $old_instance;

            /**
             * Standardwerte setzen
             *
             * @var array
             */
            $new_instance = wp_parse_args((array) $new_instance, array(
                'footer-books-title' => '',
                'footer-books-start' => '0',
                'footer-books-ende' => '5'
            ));

            /**
             * Die über das Formular getätigten Eingaben auf Richtigkeit und Schadcode prüfen
             *
             * @var array
             */
            $instance['footer-books-title'] = (string) strip_tags($new_instance['footer-books-title']);
            $instance['footer-books-start'] = (string) strip_tags($new_instance['footer-books-start']);
            $instance['footer-books-ende'] = (string) strip_tags($new_instance['footer-books-ende']);

            /**
             * Nun kann ein Array mit den Einstellungen an die verarbeitende Funktion
             * zurückgeliefert werden (Klasse WP_Widget). Die Einstellungen werden in der WP-Datenbank
             * gespeichert.
             */
            return $instance;
        } // END function update($new_instance, $old_instance)

        /**
         * Ausgabe des Widgets im Frontend.
         *
         * @see WP_Widget::widget()
         */
        public function widget($args, $instance) {
            extract($args);

            echo $before_widget;

            $title = (empty($instance['footer-books-title'])) ? '' : apply_filters('footer_books_title', $instance['footer-books-title']);

            if(!empty($title)) {
                echo $before_title . $title . $after_title;
            } // END if(!empty($title))

            // HIER nur sinnloser Beispielcode
            // Bereich für die eigentliche Tätigkeit des Widgets
            global $wpdb;

            //Beispiel für ein SQL-Statement
            $results = $wpdb->get_results("
                SELECT a.post_name, post_title
                FROM `wp_posts` AS a
                WHERE a.post_status = 'publish'
                ORDER BY post_date DESC
                LIMIT ".$instance['footer-books-start'].",".$instance['footer-books-ende']
            );

            $output = '<ul>';

            foreach($results as $row)
            {
                //Ausgabe von $row->post_name
            }                

            echo $output . '</ul>';

            echo $after_widget;
        } // END function widget($args, $instance)

        /**
         * HTML des Widgets
         *
         * @param array $args
         */
        private function my_widget_html_output($args = array()) {
            /**
             * Widgetausgabe
             * Hier wird nun das HTML für das Widget erstellt
             */
            $var_sWidetHTML = wpautop($args['my-widget-text'], true);

            return $var_sWidetHTML;
        } // private function my_widget_html_output($args = array())
    } // END class My_Test_Widget extends WP_Widget

    /**
     * Widget initialisieren.
     */
    add_action('widgets_init', create_function('', 'return register_widget("My_Test_Widget");'));
} // END if(!class_exists('My_Test_Widget'))