Bilder in Typo3 beim Speichern bearbeiten

Wie man mit Hilfe des processDatamapClass-Hooks und ImageMagick Bilder in Typo3 bereits beim Speichern (z.B. zur externen Verwendung) weiter bearbeiten kann.

Für ein Kundenprojekt wurde eine Schnittstelle geschaffen, die per Atom-Feed die redaktionellen Daten von appsundco.de einer iPhone-Applikation zur Verfügung stellt. Zunächst wurden die Grafiken, die mit dem Content verknüpft sind, direkt in Feed eingebunden. Die von den Autoren hochgeladen Bilder varrierten jedoch zum Teil stark und erforderten Rotation, Skalierung und Bearbeitung. Im Frontend des CMS ist das kein Problem, da das mit Bordmitteln von Typo3 erledigt werden kann. Für den Atom-Feed musste nun eine ähnliche Qualität der Grafiken sichergestellt werden. ImageMagick, dass neben GDLib die Grundlage der Grafik-Fähigkeiten von Typo3 bildet, bietet mehr als ausreichende Möglichkeiten.

ImageMagick

Aus Performance-Gründen fiel die Entscheidung, die Bildverarbeitung beim Speichern bzw. Ändern von Datensätzen im CMS durchzuführen, da so beim Abruf des Feeds nur statische Grafiken vom Webserver ausgeliefert werden müssen. Bei den Daten handelt es sich um Zeilen aus zwei verschiedenen Tabellen:tt_news und eine Anwendungsspezifische Tabelle, die iPhone-App-Rezensionen enthält. Es sind ausserdem Einträge aus tt_content über das Plugin rgnewsce mit den News-Zeilen verbunden.

Leider bietet MySQL keine Möglichkeit ein Shell-Script durch einen Trigger auszulösen. Deshalb musste schon im CMS auf eine Änderung in den Tabellen reagiert werden. Das Mittel der Wahl, sind sogenannte Hooks. Hooks ermöglichen es, eigenen Code im Typo3-System zu registrieren, der zu bestimmten Ereignissen ausgeführt wird. Eine Übersicht verfügbarer Hooks bietet z.B. die Extension dmc_hooklist. Der Hook processDatamapClass wird in diesem Zusammenhang, vor bzw. nach dem Speichern eines Datensatzes aufgerufen.

Zunächste empfielt es sich per Kickstarter eine neue Extension anzulegen. Hier kann in der ext_localconf.php eine eigene Klasse mit dem processDatamapClass-Hook registriert werden.

$ref = 'EXT:' . $_EXTKEY . '/class.tx_' . $_EXTKEY . '_dispatch_processdatamap.php:tx_' . $_EXTKEY . '_dispatch_processdatamap';
$GLOBALS ['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = $ref;

Wie oben zu sehen, funktioniert der Hook nur, wenn im Stammverzeichnis der Extension unterhalb von typo3conf/ext/$_EXTKEY eine PHP-Datei mit Prefix class. und dem Namen der Klasse tx_' . $_EXTKEY . '_dispatch_processdatamap.php. Hierbei enspricht $_EXTKEY dem Namen der Extension, der im Kickstarter vergeben wurde. Diese Klasse kann nun mehere Methoden definieren, die Bestimmen ob eine Benachrichtigung vor oder nach dem Speicher erfolgen soll:

class tx_myexthooks_dispatch_processdatamap {
    public function processDatamap_postProcessFieldArray ($status, $table, $id, &$fieldArray, &$pObj) {
        $this->initialize_directories();

        if ($table == 'tx_appreview_apps') {
            $this->processDatamap_onApps($id, $status, $fieldArray);
        } else if ($table == 'tt_news') {
            $this->processDatamap_onNews($id, $status, $fieldArray);
        }
    }
}

Diese Methode wird nach dem Speichern aufgerufen. Ihr wird in den Parametern $table und $id die Tabelle und der Primärschlüssel der geänderten Zeile übergeben. Im Falle eines neuen Datensatzes ist die ID (UID) gleich 0. Der Parameter $fieldArray enthält die geänderten Daten aus dem Backend. In dieser Methode kann nun aus den übergebenen Daten und Daten aus der Datenbank die Namen der Grafiken exzerpiert werden. Diese Dateinamen sollten nun noch zu vollständigen Pfaden aufgelöst werden. Anschließend kann direkt auf den Dateinamen ein ImageMagick aufruf stattfinden.

protected function prepareAppImages($appFieldArray) {
    if (! array_key_exists("image", $appFieldArray))
        return;

    $image_list = explode(",", $appFieldArray["image"]);

    foreach ($image_list as $image) {
        $image_src = $this->getAppImageSourcePath($image);
        $image_trg = $this->getAppImageTargetPath($image);

        if (file_exists($image_src) == FALSE) {
            continue;
        }

        $cmd = $this->createIMCommandlineForAppImage($image_src, $image_trg);
        exec($cmd);
    }
}

Interessant ist jetzt der Aufruf von createIMCommandlineForAppImage, der die Kommandozeile zum Aufruf des convert-Kommandos erstellt.

function createIMCommandlineForAppImage($source_file, $target_file) {
    $parameters  = "-resize " . $this->image_geometries["app_icon"] . " ";
    if (TYPO3_OS == 'WIN')
        $parameters .= "-bordercolor white -border 1x1 -matte -fill none -fuzz 5% -draw \"matte 0,0 floodfill\" -shave 1x1";
    else
        $parameters .= "-bordercolor white -border 1x1 -matte -fill none -fuzz 5% -draw 'matte 0,0 floodfill' -shave 1x1";

    return $this->createIMCommandline($source_file, $target_file, $parameters);
}

function createIMCommandline($source_file, $target_file, $parameters) {
    $convert_cmd = $this->getIMConvertCommand();
    if ($convert_cmd == false) {
        return false;
    }

    $cmd = sprintf("%s %s %s %s", $convert_cmd, $source_file, $parameters, $target_file);
    return $cmd;
}

function getIMConvertCommand() {
    $gfx_conf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
    $im_path = rtrim($gfx_conf['im_path'], DIRECTORY_SEPARATOR);

    $cmd_ext = (TYPO3_OS == 'WIN' ? '.exe' : '');
    $cmd = "convert" . $cmd_ext;

    return realpath(sprintf("%s%s%s", $im_path, DIRECTORY_SEPARATOR, $cmd ));
}

Der Pfad zum convert-Kommando wird aus den globalen Typo3-Einstellungen gelesen. Die oben verwendeten ImageMagick Kommandozeilen-Parameter skalieren das Bild auf eine vorgegebene Breite unter Einhaltung des Seitenverhältnisses. Ausserdem wird ggf. ein einfarbiger Hintergrund entfernt, das Bild also automatisch Freigestellt.