Fontello – Custom Icon-Fonts schnell erstellt

Font Awesome ist zu schwer wenn man nur mal schnell eine Hand voll Icons benötigt? Bei Entypo oder Typicons ist nicht das richtige Icon dabei?

Kein Problem, mit Fontello lassen sich Glyphen aus einer ganzen Reihe von Icon-Fonts auswählen und zu einem neuen Font zusammen stellen. Ein Klick und man bekommt die Schriftart in verschiedenen Formaten samt der benötigten CSS-Datei direkt auf die Festplatte heruntergeladen.

Je nach benötigtem Umfang kommt man so mit wenigen Kilobytes aus – eine satte Einsparung gegenüber z. B. dem recht schwergewichtigen Font Awesome in Vollausstattung.

Bestimmten und reproduzierbaren Anteil von Datensätzen für Verarbeitung selektieren

Ich tat mich sehr schwer mit der Überschrift dieses Beitrags, da das Problem nicht in einem kurzen Satz zu beschreiben ist. Daher noch mal in Ruhe:

Problemstellung

Gegeben sei eine große Zahl von Bildern, welche mit verhältnismäßig hohem Aufwand an Ressourcen skaliert werden sollen. Das alles muss on-demand passieren, wenn ein Webbrowser ein Bild aufruft.

Ist ein Bild einmal skaliert, wird das Ergebnis zwischengespeichert, ein erneuter Aufruf erzeugt also keinen relevanten Aufwand. Da die Bilder in großer Zahl parallel aufgerufen werden, soll nur ein gewisser Anteil die Skalierung durchlaufen und es sollen auch immer die selben Bilder sein. Eine zufällige Auswahl scheidet also aus.

Nach und nach kann der Prozentwert erhöht werden, bis irgendwann alle Bilder skaliert sind.

Lösung

Ich habe das Problem auf die folgende Art gelöst:

  1. Wenn der Zielwert 100 % ist, wird das Bild sofort in die Weiterverarbeitung gegeben, die folgende Prüfung findet nicht statt
  2. Wenn der Zielwert 0 % ist, wird das Bild nicht weiterverarbeitet, die folgende Prüfung findet nicht statt
  3. Für alle Zielwerte größer 0 % und kleiner 100 % wird die CRC32-Checksumme der Bild-URL (die URL ist das Identifikationsmerkmal des Bildes) berechnet, das Ergebnis ist ein 32 Bit-Integer
  4. Berechnung von Checksumme Modulo 100 (ergibt die letzen beiden Stellen der Checksumme)
  5. Prüfen, ob das Ergebnis aus dem vorherigen Schritt kleiner als der angestrebte Prozentwert des Anteils zu skalierender Bilder ist, falls ja Skalierung starten, sonst übergehen

Die Verteilung der der CRC32-Checksummen im Raum der 32-Bit-Integer-Zahlen ist für diesen Zweck halbwegs gleichmäßig und zufällig. Die letzten beiden Stellen der Zahl sind also auch ausreichend gleichverteilt zwischen 0 und 100. Man kann mit dieser Methode daher relativ genau und mit wenig Aufwand einen bestimmten und reproduzierbaren Anteil der Datensätze (Bilder) selektieren.

Beispiel

Es sollen 20 % aller Bild-URLs skaliert werden.

Gegeben sei die folgende Bild-URL:

https://www.marcusjaschen.de/wp-content/uploads/2019/03/2019-03-31-DJI_0147.jpg

Die CRC32-Checksumme dieser URL ist 3556399431. Modulo 100 ergibt sich ein Ergebnis von 31.

31 ist nicht kleiner als der Zielwert 20, das Bild wird also nicht skaliert.

Eine andere Bild-URL wäre:

https://www.marcusjaschen.de/wp-content/uploads/2019/03/2019-03-31-DJI_0150.jpg

Hier lautet die Checksumme 2860930802. Modulo 100 erhält man 2, was kleiner als 20 ist, dieses Bild würde also weiterverarbeitet werden.

phpbrew mit macOS 10.14 Mojave

Seit Mojave baut phpbrew auf meinen Rechnern die Binaries nicht mehr, sondern bricht mit einer Fehlermeldung ab:

% phpbrew -d install -j 4 7.2.13 \
+default \
+iconv \
+icu \
+bz2 \
+zlib \
+curl

[...]

checking for BZip2 in default path... not found
configure: error: Please reinstall the BZip2 distribution

Das Problem betrifft neben bzip2 auch noch weitere Libraries wie iconv, zlib und curl und entsteht vermutlich im Zusammenhang mit Homebrew. Die benötigten Includes werden im Homebrew-Universum gesucht, statt auf die vom System bereitgestellten zurückzugreifen. Hat man die entsprechenden Libraries nicht per Homebrew installiert, kommt es zu diesen Fehlern.

Das Problem lässt sich allerdings sehr einfach beheben, indem man zum einen die benötigten Libraries per Homebrew installiert:

% brew install zlib bzip2 libiconv curl

Zum anderen gibt man phpbrew nun die passenden Include-Pfade mit:

% phpbrew -d install -j 4 7.2.13 \
+default \
+iconv=/usr/local/opt/libiconv \
+icu \
+bz2=/usr/local/opt/bzip2 \
+zlib=/usr/local/opt/zlib \
+curl=/usr/local/opt/curl

[...]

Congratulations! Now you have PHP with 7.2.13 as php-7.2.13

PHP-Extensions benötigen unter Umständen ebenfalls ein paar Extra-Infos zum Kompilieren, für gd nutze ich z. B. folgendes Kommando:

% phpbrew use 7.2.13 && phpbrew ext install gd -- \
--with-zlib-dir=/usr/local/opt/zlib

Bilder von Remote laden in WordPress-Entwicklungsumgebung

Eben stolperte ich über einen Blogpost, in dem erklärt wird, wie man in WordPress die Bilder von einem anderen Host als dem aktuellen einbindet. Normalerweise geht WordPress davon aus, dass die hochgeladenen Medien auf dem aktuellen Host unterhalb von /wp-content/uploads/ zu finden sind.

Beispiel: Dieses Blog läuft auf https:/www.marcusjaschen.de/ und die hochgeladenen Bilder sind normalerweise irgendwo unterhalb von https:/www.marcusjaschen.de/wp-content/uploads/ zu finden.

Holt man sich jetzt die WordPress-Installation in eine Entwicklungsumgebung, möchte man unter Umständen nicht alle Bilder und Medien mitkopieren, denn das können je nach Website schnell einige Gigabytes werden. Die Medien sollen aber natürlich trotzdem angezeigt werden.

Der erwähnte Blogpost beschreibt eine Lösung, die darauf beruht, die Basis-URL für das Uploads-Verzeichnis über eine versteckte Config-Option in WordPress zu ändern. WordPress schreibt dann automatisch alle Medien-URLs um, so dass der Browser diese von ihrer originalen Position lädt (oder aus einem CDN).

Diese Config-Option war mir bis eben unbekannt. Ich habe das Problem bisher auf eine andere Art und Weise gelöst. In meinen nginx-Konfigurationen habe ich jeweils eine Rewrite-Regel, die einfach die Requests umschreibt:

location /wp-content/uploads/ {
    rewrite ^(/wp-content/uploads/.*)$ https://www.marcusjaschen.de/$1 last;
}

Der beobachtete Effekt ist bei beiden Lösungen identisch.

Mein erstes veröffentlichtes WordPress-Plugin

Ich habe im Laufe der Zeit unzählige WordPress-Plugins entwickelt, bisher ausschließlich aber für die interne Verwendung in Projekten wie z. B. MTB-News.de.

Eine Veröffentlichung im Plugin-Verzeichnis auf wordpress.org kam bisher nicht in Frage, da die Einsatzzwecke sehr eng an spezielle Setups gebunden waren und die Plugins damit für jede andere Website schlicht nicht von Nutzen wären.

Nun war es an der Zeit, auch mal ein Plugin zu veröffentlichen, welches durchaus weitere Nutzer haben könnte. Es stellt die Verbindung zwischen zwei anderen beliebten Plugins her:

  • Better Internal Link Search verbessert die Suchfunktion beim Verlinken auf bestehende Inhalte
  • WP Subtitle erweitert WordPress-Artikel um eine zweite Überschrift („Subtitle“, „Dachzeile“)

Better Internal Link Search findet bei der Suche keine Artikel, die den Suchbegriff im Subtitle enthalten. Es bietet allerdings an der richtigen Stelle einen Filter-Hook, den ich genutzt habe, um die Suche entsprechend zu erweitern.

Die Suche ohne das Plugin findet keine Ergebnisse, da der Suchbegriff nur in einem Subtitle vorkommt.

Mit aktiviertem Plugin wird der gesuchte Artikel sofort gefunden.

Das Plugin ist momentan bei MTB-News.de, eMTB-News.de und Rennrad-News.de im Einsatz und ich habe mir versichern lassen, dass es eine große Hilfe in der täglichen Arbeit der Redaktion ist.

Alle Infos zum Plugin gibt es im Plugin-Verzeichnis von WordPress. Der Source Code ist bei Github zu finden.

Entwickler oder Entwicklerinnen die sich besonders schlau vorkommen …

… bauen gerne mal Konstrukte der folgenden Art:

$path = ltrim( end( @explode( get_template(), str_replace( '\\', '/', dirname( __FILE__ ) ) ) ), '/' );

Sortiert sieht es dann so aus:

$path = ltrim(
    end(
        @explode(
            get_template(),
            str_replace('\\', '/', dirname(__FILE__))
        )
    ), 
    '/'
);

Zum Kontext: diese Zeile stammt aus einem kommerziellen WordPress-Template und es soll der letzte Teil des Pfadnamens des aktuell ausgeführten Skripts bestimmt werden. Aus /var/www/wordpress/wp-content/themes/valenti/option-tree soll also option-tree werden.

Von innen nach außen:

  • dirname(__FILE__) liefert den Pfadanteil des aktuell ausgeführten PHP-Skriptes; aus /var/www/wordpress/wp-content/themes/valenti/option-tree/ot-loader.php wird also /var/www/wordpress/wp-content/themes/valenti/option-tree
  • str_replace('\\', '/', …) ersetzt den Backslash (vermutlich als Workaround für Windows-Systeme) mit einem Slash, der Pfad bleibt auf einem unixoiden System also erstmal unverändert (es sei denn, er enthält tatsächlich den Backslash)
  • get_template() liefert den Namen des aktiven WordPress-Templates, in diesem Fall valenti
  • explode() trennt den Pfadnamen in einzelne Teile, und zwar an jeder Stelle an der der String valenti vorkommt; man erhält also dieses Array: [0 => '/var/www/wordpress/wp-content/themes/', 1 => '/option-tree']
  • end() liefert den Wert des letzten Elements des Arrays: /option-tree
  • ltrim(…, '/') entfernt zum Schluss den Slash am Anfang des Strings, so dass wir das Ergebnis erhalten: option-tree

Soweit, so gut.

Diese Variante hat unter Umständen einige Probleme, Backslashes im Pfadnamen könnten eines davon sein. Unterdrückte Fehlermeldungen mit dem @-Operator könnte zu weiteren Hässlichkeiten beim Debugging führen.

Aber das Hauptproblem ist, dass der Entwickler oder die Entwicklerin hier eine komplexe Lösung für ein einfaches Problem geschaffen hat. PHP löst die gegebene Fragestellung nämlich mit Bordmitteln viel eleganter und zudem fehlerunanfällig und OS-unabhängig:

$path = basename(__DIR__);

Keine Ursache.

Pipes, oder: eine von vielen Dingen, die mich an Unix begeistern

Oft benötige ich für meine lokale Entwicklungsumgebung die aktuelle Struktur und die Inhalte der Datenbank des Produktivsystems. Ein möglicher Ablauf, um die Daten aus dem Produktiv-Datenbankserver herauszuholen und lokal zu importieren könnte so aussehen:

  1. Einloggen auf dem Server
  2. per mysqldump eine Datei mit allen SQL-Statements zum Erzeugen der Datenbank samt ihrer Daten schreiben
  3. Den Dump mit gzip oder bzip2 komprimieren
  4. Diese Datei mit scp oder rsync auf den lokalen Rechner herunterladen und in die Entwicklungs-Virtual-Machine kopieren
  5. Einloggen auf der Entwicklungs-VM
  6. Entpacken sowie Importieren des Dumps

Oder man macht es einfach als Einzeiler auf der Shell:

ssh server "mysqldump db_name | gzip -c" | ssh vagrant "gzip -dc | mysql db_name"

Wahlweise kann man auch noch pv als Fortschrittsanzeige dazwischenschalten:

ssh server "mysqldump db_name | gzip -c" | pv | ssh vagrant "gzip -dc | mysql db_name"

Mit Hilfe des Pipes-Konzepts lässt sich der Ausgabestream eines Kommandos als Eingabestream eines anderen Kommandos verwenden, ohne dass temporäre Dateien angelegt und herumkopiert werden müssen. Es dürfte sich neben der „Everything is a file“-Philosophie¹ um eines der praktischsten Dinge von Unix sein.

¹ Eine Pipe hat übrigens auch einen file descriptor.

Workaround für PHP Bug #43225

In PHP gibt es einen Bug, der dazu führt, dass die Funktion fputcsv unter Umständen fehlerhaftes CSV generiert, welches dann von Programmen nicht mehr eingelesen werden kann.

Der Fehler tritt auf, wenn in einem Feldwert ein Backslash \ vorkommt, der direkt von einem doppelten Anführungszeichen " („double quote“) gefolgt wird. Ein Beispiel:

$data = [
    'CMXINV',
    '-1001338',
    'Some string data here: "quoted text // highlight string \\\\" and "foo" bar',
    'baz'
];

Hier folgt auf die zwei Backslashes[1] direkt ein doppeltes Anführungszeichen.

Erzeugt man aus dem $data-Array mit fputcsv eine CSV-Zeile, erwartet man folgende Ausgabe:

CMXINV;-1001338;"Some string data here: ""quoted text // highlight string \\"" and ""foo"" bar";baz

Tatsächlich erhält man aber:

CMXINV;-1001338;"Some string data here: ""quoted text // highlight string \\" and ""foo"" bar";baz

Bei genauem Hinschauen erkennt man, dass hinter den beiden Backslashes nur ein doppeltes Anführungszeichen steht, obwohl es zwei sein sollten[2].

Dieses Verhalten ist als PHP Bug 43225 bereits 2007 dokumentiert worden und wurde bis heute nicht korrigiert.

Ich setze fputcsv in meiner Open Source Library für Collmex ein. Jegliche Kommunikation mit der Collmex-API geschieht über das Senden und Empfangen von Nachrichten im CSV-Format. Bei einem Projekt trat nun der Fall auf, dass in einem Feld tatsächlich Backslashes vor doppelten Anführungszeichen standen und Collmex den erzeugten fehlerhaften Datensatz nicht lesen konnte. Ich habe jetzt einen Workaround in der Collmex-Library eingebaut, mit der dieser spezielle Fehler nicht mehr auftritt.

Der Workaround macht nichts anderes als vor dem Aufruf von fputcsv temporär einen String zwischen Backslash und doppeltem Anführungszeichen einzufügen und ihn nach dem Erzeugen der CSV-Daten wieder zu entfernen. Die Details lassen sich in der betreffenden Quelldatei bei Github ansehen.


[1] Es sind hier vier Backslashes zu sehen, weil das Backslash-Zeichen zum „escapen“ benutzt wird und selbst auch escaped werden muss – durch einen weiteren Backslash.
[2] Ähnlich wie bei [1] muss ein doppeltes Anführungszeichen durch ein weiteres doppeltes Anführungszeichen „escaped“ werden, wenn die Feldwerte durch ein doppeltes Anführungszeichen eingeschlossen werden.