Auch Entwickler brauchen Platz zum Spielen

2. April 2015 /

Gravatar-Bilder in WordPress cachen: Ohne Plugins und Cronjobs

Im Kommentarbereich abgebildete Avatare gestalten die Kommunikation zwischen Leser und Autor ein Stückchen persönlicher, wiedererkennbarer. Ein Grund mehr, Lesermeinungen mit personalisierten Bildern auf Gravatar.com zu verknüpfen. Zudem kommt heutzutage kaum ein WordPress Theme ohne dieser visuellen Funktionalität aus. Im aktuellen Tutorial der Serie „Pimp my blog“ wird eine ausgereifte, hier auf Playground durch zahlreiche Tests gelaufene Lösung angeboten, Avatar-Bilder direkt im Blog zu speichern. Für weniger Abhängigkeit und mehr Kontrolle. Ohne gravierende Anpassungen in den Templates, ohne externe Cronjobs. Mit integrierter PNG-Komprimierung.

All in One Pack für Gravatar Cache
Auf etlichen Blogs installierte Plugins oder selbst gestrickte Ansätze sind entweder zu monströs, zu pflegeintensiv oder setzen einen (externen oder Server-eigenen) Cronjob voraus – für viele Blogbetreiber ein KO-Kriterium. Die unten ausführlich beschriebene Methode bringt alles Notwendige mit, weist einen etwas anderen Denkansatz auf und ist ein feines, effizientes Zusammenspiel zwischen WordPress, PHP und dem Server.

Doch welche Vorteile bringt eine Zwischenspeicherung der Gravatar-Abbildungen auf dem eigenen Server? Wozu der lokale Cache? Einige Überlegungen:

Pluspunkte

  • Unabhängigkeit vom Hoster der Bilder (Nichterreichbarkeit, Auslastung etc.)
  • Steuerung des Cache-Zeitraumes und der Komprimierung
  • Keine Umleitungen zum Default-Gravatar bei nicht existentem Bild (Warnung in Google Webmaster-Tools)

Minuspunkte

  • Enormer Ressourcenverbrauch (Speicher, Rechenleistung, Bandbreite etc.)
  • Geminderte Aktualität des Motivs
  • Anzahl der Anfragen pro Domain erhöht sich (Stichwort „Parallele Verbindungen“)

Standard-Gravatar: Google warnt vor Umleitungen
Standard-Gravatar: Google warnt vor Umleitungen

Zeitnahe Auffrischung der Profil-Bilder
Hat man sich als Blog-Administrator entschieden, Avatar-Grafiken im Blog-Verzeichnis abzulegen, so muss auch die Aktualisierung des Materials in kurzen Zeitabständen sichergestellt werden. Aus Respekt zum Leser.

Wo grade das Thema „Gültigkeit des Bildes“ angesprochen wurde… Werden bei der Auslieferung der Grafiken bestimmte Cache-Header mitgeschickt (.htaccess lässt grüßen), so gehört das Gültigkeitsdatum (= Aufbewahrung im Browsercache) dem Aktualisierungsrhythmus auf dem Server entsprechend angepasst. Andernfalls bringt die regelmäßige Erneuerung der Daten im Cache-Verzeichnis nicht den gewünschten Effekt, da der Browser auf Befehl der gesendeten Header bis auf Weiteres das alte, nicht aktualisierte Image darstellt. Der Code-Schnipsel für die Cache-Header befindet sich am Ende des Beitrags.

Wie ist das technische Konzept?

  • Avatare im Blog werden nur noch aus dem lokalen Verzeichnis geladen.
  • Ist das Bild lokal vorhanden (= gecached), wird es abgebildet, andernfalls…
  • Eine Regel in der .htaccess erkennt das fehlende Bild und ruft ein PHP-Script auf.
  • Das von WordPress losgelöste Script lädt die Grafik von gravatar.com runter.
  • Besitzt der Nutzer ein Gravatar-Bild, wird es lokal gespeichert, sonst = Standard-Bild.
  • Im festgelegten Zeitfenster löscht WordPress veraltete Bilder aus dem Verzeichnis.

Das technische Konzept in Sätzen…
Soll also ein Kommentator sein Avatar-Bild angezeigt bekommen, prüft zunächst der Browser, ob die Datei im Browsercache je nach Header-Vorgaben noch brauchbar ist. Wenn nicht, wird sie vom Blog-Server angefordert (extra Verzeichnis wird dafür angelegt). Existiert die Datei an dem Ort, kommt sie zum Browser samt Cache-Header zurück und wird wie erwartet abgebildet. Fehlt dagegen die Grafik auf dem Server, wird diese Ungereimtheit (das Bild war noch nie da oder wurde zwecks Aktualität entfernt) erkannt und an das zuständige PHP-Script weitergeleitet, welches sich um die Angelegenheit kümmert und ein Bild (Gravatar oder Default) zur Anzeige zurückgibt.

Ganz schön sparsam und effizient die Technik, denn das Aktualisierungsskript wird erst bei Bedarf „angeworfen“ und nicht bei jedem Abruf des Avatar-Bildes. Das Bild wird aus HTML heraus auf gewöhnliche Weise referenziert.

Im WordPress-Blog gespeicherter Gravatar
Lokale Referenzierung: Im WordPress-Blog gespeicherter Gravatar

Schritt-für-Schritt-Anleitung für lokale Avatar-Bilder
Da die Inbetriebnahme der Lösung nicht trivial ist, wurde sie in mehrere Arbeitsschritte aufgeteilt. Bitte Zeit und Ausdauer mitbringen…

1. Anlegen des Cache-Verzeichnisses
Zunächst wird der Aufbewahrungsort für die lokal abgelegten Grafiken definiert und vorbereitet. Der Ordner wp-content eignet sich perfekt für diesen Zweck, da dort eh multimediale, Theme-bezogene Dateien aufbewahrt werden. gravatar ist der passende Name für ein Unterverzeichnis mit unserem Vorhaben.

Das per FTP angelegte Verzeichnis beinhaltet seinerseits vier weitere Elemente, die für die Generierung und Sicherung der Gravatare verantwortlich sind.

Kurzdarstellung der Objekte

  • default.png: Gravatar-Bild als Ersatz für fehlendes Nutzerbild
  • cache: Ordner als Zielort für generierte PNG’s
  • .htaccess: Serverseitige Einstellungen
  • cache.php: PHP-Skript für die Erzeugung der Avatare

WordPress Gravatar-Cache
Ordnerinhalt für den Gravatar-Cache

Datei default.png
Standard-Avatar für nicht existente Gravatar-Accounts oder Accounts ohne eine zugewiesene Illustration. Im PNG-Format und in Templates/CSS verwendeter Bildgröße (beispielsweise 64×64 Pixel).

Ordner cache
Sammelstelle der erzeugten Avatar-Bilder für die spätere Einbindung ins Theme. Wichtig: Schreibrechte notwendig.

Der daraus resultierende Pfad der Profilbilder wäre entsprechend http://wp.tld/wp-content/gravatar/cache/xxx.png, wobei xxx den Hash-Wert der E-Mail-Adresse eines Nutzers zu vertreten hätte. Beim ersten Blick fällt auf: Die Pfadzusammenstellung ist eindeutig zu lang, lässt sich in der .htaccess des Blog-Hauptverzeichnisses jedoch wunderbar kürzen.

Kurz & bündig: Pfad zum lokalen Gravatar

RewriteEngine On
RewriteRule ^gravatar/(.*).png$ /wp-content/gravatar/cache/$1.png [L]

Nach der Einrichtung der Umleitung können Gravatare via http://wp.tld/gravatar/xxx.png abgerufen und dargestellt werden. Mit dieser Pfadstruktur wird nachfolgend auch tatsächlich gearbeitet.

Datei .htaccess
Die Systemdatei beinhaltet wichtige Informationen für den Cache-Ordner und das Gesamtkonzept. Der Inhalt der Datei ist vorgegeben und bedarf keinerlei Modifizierungen.

Inhalt der .htaccess Datei im gravatar Ordner

# Kein Browsing
Options -Indexes

# Engine on
RewriteEngine On

# Gravatar laden
RewriteCond %{REQUEST_URI} .png$ [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^.*$ cache.php?file=%{REQUEST_URI} [L]

Die ersten Zeilen stehen für mehr Datenschutz und unterbinden das sogenannte Verzeichnis-Browsing (Kein Eintrag notwendig, wenn vom Hoster auf der Serverebene bereits deaktiviert). Der übrige Code sorgt für eine interne Weiterleitung und den Aufruf des PHP-Skriptes cache.php, falls ein Gravatar-Bild im Cache-Ordner nicht verfügbar sein sollte und aufs Neue generiert werden muss. Das Prinzip einer 404-Seite.

Datei cache.php
Von WordPress unabhägige PHP-Datei, die für die Abholung, Generierung und Speicherung des Gravatars zuständig ist. Einzige manuelle Anpassung innerhalb der Datei (falls abweichend): die gewünschte Bildgröße (Standardwert liegt bei 64 Pixel). Der Rest ist sofort einsatzbereit und kann als Datei hochgeladen werden.

Vollständiger Content der cache.php Datei

<?php
// Autor: Sergej Müller
// Datei: cache.php


/* Kein Input? */
if (empty($_GET['file'])) {
  exit();
}

/* Hash extrahieren */
$hash = basename($_GET['file'], '.png');

/* Richtiges Format? */
if (!preg_match('#^[a-f0-9]{32}$#i', $hash)) {
  exit();
}

/* Gravatar */
$gravatar = sprintf(
  'http://www.gravatar.com/avatar/%s.png?s=%d&d=404',
  $hash,
  64 // Gravatar-Breite
);

/* Filename */
$filename = dirname($_SERVER['SCRIPT_FILENAME']);

/* Cachefile */
$cache = sprintf(
  '%s/cache/%s.png',
  $filename,
  $hash
);

/* Gravatar holen */
$source = @file_get_contents($gravatar);

/* Default */
if (!$source) {
  $source = @file_get_contents(
    sprintf(
      '%s/default.png',
      $filename
    )
  );

/* Optimieren */
} else {
  $ysmush = json_decode(
    @file_get_contents(
      sprintf(
        'http://ws1.adq.ac4.yahoo.com/ysmush.it/ws.php?img=%s',
        urlencode($gravatar)
      )
    )
  );
  
  if ($ysmush && !empty($ysmush->dest)) {
    $source = @file_get_contents(urldecode($ysmush->dest));
  }
}

/* Speichern */
$response = @file_put_contents(
  $cache,
  $source
);

/* Fehlerhaft? */
if (!$response) {
  exit();
}

/* Header senden */
header('Content-Type: image/png');
header('Content-Length: ' .$response);

/* Ausgabe */
echo $source;

2. Modifikation der Templates
Nun ist der simpelste Meilenstein der Umsezung erreicht: Einzeilige Abänderung des Templates comments.php und eine Verfollständigung der Datei functions.php um eine selbstgeschriebene WordPress-Funktion bzw. Filter samt Aufruf.

Vervollständigung der functions.php des aktuellen Theme

/**
* Gibt den gecachten Gravatar zurück
*
* @author  Sergej Müller
* @param   string   $avatar  Original-Avatar
* @param   object   $comment  Aktueller Kommentar
* @param   integer  $size  Gewünschte Größe
* @return  string   IMG-Tag mit gecachtem Gravatar
*/

function sm_get_avatar($avatar, $comment, $size) {
  /* Leer? */
  if ( !is_object($comment) ) {
    return $avatar;
  }
  
  /* Modify */
  return sprintf(
    '<img src="/gravatar/%s.png" width="%2$d" height="%2$d" class="avatar" alt="%3$s" />',
    md5(strtolower(trim($comment->comment_author_email))),
    $size,
    esc_attr($comment->comment_author)
  );
}

add_filter(
  'get_avatar',
  'sm_get_avatar',
  1,
  3
);

/* Aufräumen */
if (!get_transient('sm_avatar_cron')) {
  if ($files = glob(WP_CONTENT_DIR. '/gravatar/cache/*.png')) {
    foreach ($files as $file) {
      if (time() - @filemtime($file) > 60 * 60 * 24 * 2) { /* 2 Tage Aufbewahrung */
        @unlink($file);
      }
    }
  }
  
  set_transient(
    'sm_avatar_cron',
    'ilovesweta',
    60 * 60 * 24
  );
}

Die erste Funktion im obigen Quelltext ist weniger spektakulär, ist ein Filter auf die WordPress-eigene Funktion get_avatar und sorgt für die Ausgabe der Avatar-Abbildung aus dem lokalen Ordner. Breite, Höhe, CSS-Klasse, Alt-Tag – alles Wirksame dabei. Als Ergänzung wäre eine separate, Cookie-lose Subdomain empfehlenswert.

Die get_transient Abfrage (mit dem Platzhalter der besonderen Art ;)) ist der ganze Stolz der Anwendung: Sie imitiert einen intern ausgeführten Cronjob und bereinigt den angelegten Cache-Ordner, indem veraltete Dateien (älter als X Tage, 2 als Default) eliminiert werden. Das System kontrolliert täglich das Verzeichnis und hält den Inhalt des Cache-Ordners so klein (= aktuell) wie nötig. Wird daraufhin eine Artikelseite mit Kommentaren aufgerufen und die passenden Gravatare fehlen auf dem Server (noch nie da gewesen oder entfernt, weil zu alt), so generiert und sichert das PHP-Skript ein frisches Nutzer-Bild.

Gespeicherten Avataren den letzten Schliff verpassen
Nachdem alle Vorbereitungen im Hintergrund getroffen wurden, ist es an der Zeit die Lösung in Betrieb zu nehmen: Anzeige der Gravatare im Frontend. Für diesen Zweck soll das Theme-Template comments.php minimal angepasst werden. Je nach Theme ist keine Berichtigung der Code-Zeilen notwendig, da der Aufbau bereits in richtiger Form vorliegt. Dazu zählen Themes mit der Einbindung der Kommentare via wp_list_comments, Twenty Ten gehört beispielsweise dazu.

Zum Verständnis der Unterschied des Funktionsaufrufs: Der erste Parameter ist nicht wie üblich die E-Mail-Adresse des Kommentators, vielmehr wird der komplette Kommentar als Objekt angehängt.

Avatar-Einblendung im Theme per Befehl

<?php echo get_avatar($comment, 64); ?>

Optionaler Cache-Header für den Browser
Die Datei .htaccess wird auf Wunsch um die Ausgabe eines Cache-Headers erweitert, welcher beim Erstaufruf der Avatare dem Browser die Gültigkeitsdauer der Grafik kommuniziert. Der Zeitraum kann beim Einfügen der Zeilen in die .htaccess nach eigenem Ermessen justiert werden (synchron zu der functions.php, sonst wirkungslos). Zu beachten: Kleinere Tageszahl bedeutet zeitlich begrenzten Cache, bedeutet aktuellere Gravatare, bedeutet öftere Updates.

Durch diese Technik werden die Anzahl der Server-Anfragen und das Traffic-Volumen reduziert, die Ladegeschwindigkeit gestiegen. Ist das dafür vorausgesetzte Modul mod_expires auf dem Server nicht installiert, kann eine andere Methode zum Cachen der PNG-Bilder initialisiert werden.

Speicherung im Browser für 2 Tage

<ifmodule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/png "access plus 2 days"
</ifmodule>

Indexierung ausschließen
Damit Google die im Blog-Verzeichnis gesicherten Avatare nicht in die Suchergebnisse aufnimmt, ist folgender Eintrag in die Datei robots.txt zu empfehlen.

Disallow: /gravatar/

Cache-Avatar des Beitragsautors
Code-Beispiel für den Zugriff auf das gespeicherte Bild des Beitragsautors.

<img src="/gravatar/<?php echo md5(strtolower(trim(get_the_author_email()))) ?>.png" width="64" height="64" alt="Avatar" />

Mindestvoraussetzungen

  • PHP 5.2
  • WordPress 2.8.0
  • Ausgehende Serververbindungen

Zusammenfassung
Interessanter, reizvoller Ansatz mit technischen Höhepunkten. Perfekt kombiniert und durch die verwendete Kompressionstechnologie zusätzlich komprimiert. Viel Spaß beim Implementieren und Kommentieren.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.