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.