Contao-Update: CSS-Klassen in MySQL automatisiert ersetzen

Anleitung für Contao 5.3

Das Stylesheet ist umgestellt, alle .ce_-Klassen heißen jetzt .content- – und trotzdem sieht die Hälfte der Seiten kaputt aus. Der Grund versteckt sich eine Ebene tiefer: Klassennamen leben nicht nur in CSS-Dateien, sondern auch in der MySQL-Datenbank – in Inhaltselementen, Modulen, Layouts und Formularfeldern. Wer sie dort von Hand nachzieht, klickt sich durch hunderte Datensätze und übersieht garantiert welche.

Der bessere Weg: die Ersetzung automatisieren – per PHP-Skript oder direkt mit gezielten SQL-Befehlen. Wie das sicher funktioniert, inklusive Trockenlauf und Rückweg, zeigt dieser Beitrag.

Tabellen und Spalten mit gespeicherten Klassennamen

Klassennamen können in unterschiedlichen Tabellen und Spalten gespeichert sein. Häufig betroffen sind:

  • tl_content (z. B. Feld text oder rsce_data)
  • tl_module (z. B. Felder html, cssID)
  • tl_layout (z. B. Feld cssClass)
  • tl_form_field (z. B. Feld class)

Besonders bei den Rocksolid Custom Elements ist Vorsicht geboten, da deren Konfiguration in einem Blob gespeichert wird. Dennoch ist ein automatisches Ersetzen im Feld rsce_data möglich.

Automatisierte Ersetzung mit PHP

Das folgende Skript stellt eine Verbindung zur Datenbank her und ersetzt definierte Werte in den entsprechenden Tabellen und Spalten.

<?php
// Datenbankverbindung herstellen (Zugangsdaten anpassen)
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "database";
$conn = new mysqli($servername, $username, $password, $dbname);

// Fehlerprüfung bei der Verbindung
if ($conn->connect_error) {
  die("Verbindung fehlgeschlagen: " . $conn->connect_error);
}

// Liste der zu ersetzenden Werte und deren neuen Werte
$replacements = array(
  array('table' => 'tl_content', 'column' => 'text', 'old_value' => 'css_klasse', 'new_value' => 'css-klasse'),
  array('table' => 'tl_module', 'column' => 'html', 'old_value' => 'css_klasse', 'new_value' => 'css-klasse'),
  array('table' => 'tl_module', 'column' => 'cssID', 'old_value' => 'css_klasse', 'new_value' => 'css-klasse'),
  array('table' => 'tl_layout', 'column' => 'cssClass', 'old_value' => 'css_klasse', 'new_value' => 'css-klasse'),
  array('table' => 'tl_form_field', 'column' => 'class', 'old_value' => 'css_klasse', 'new_value' => 'css-klasse'),
  array('table' => 'tl_content', 'column' => 'rsce_data', 'old_value' => 'css_klasse', 'new_value' => 'css-klasse')
);

// Durchlaufen der Liste und Ausführen der Aktualisierungen
foreach ($replacements as $replacement) {
  $table = $replacement["table"];
  $column = $replacement["column"];
  $old_value = $replacement["old_value"];
  $new_value = $replacement["new_value"];

  $sql = "UPDATE `$table` SET `$column` = REPLACE(`$column`, '$old_value', '$new_value')";

  if ($conn->query($sql) === TRUE) {
    echo "Tabelle '$table', Spalte '$column': '$old_value' erfolgreich ersetzt durch '$new_value'<br>";
  } else {
    echo "Fehler beim Aktualisieren von '$table', Spalte '$column': " . $conn->error . "<br>";
  }
}

// Verbindung schließen
$conn->close();
?>

Vorteile dieser Methode

  • Alle relevanten Tabellen und Spalten lassen sich in einer zentralen Liste definieren.
  • Die Ersetzungen erfolgen automatisiert und einheitlich.
  • Auch komplexere Felder wie rsce_data in Rocksolid Custom Elements können berücksichtigt werden.

Update 2026: sicher ersetzen – mit Trockenlauf und Wortgrenzen

Update 2026: Direkte UPDATE-Befehle auf der Datenbank sind schnell – und unbarmherzig. Ein vergessenes WHERE oder ein zu gieriges Suchmuster richtet in Sekunden Schaden an, den nur ein Backup heilt. Diese Variante arbeitet mit Trockenlauf, exakten Wortgrenzen und Rückweg.

Schritt 1: Backup – ohne Ausnahme

mysqldump -u USER -p DATENBANK > vor-klassenumbau-$(date +%F-%H%M).sql

Schritt 2: Trockenlauf – erst sehen, was passieren würde

-- Wie viele Datensätze wären betroffen? (nichts wird verändert)
SELECT id, type, LEFT(cssID, 120) AS vorher
FROM tl_content
WHERE cssID REGEXP '(^|[^a-z0-9_-])alt-klasse([^a-z0-9_-]|$)'
LIMIT 50;

-- Zähler für das Protokoll
SELECT COUNT(*) AS treffer FROM tl_content
WHERE cssID REGEXP '(^|[^a-z0-9_-])alt-klasse([^a-z0-9_-]|$)';

Der Ausdruck mit den Wortgrenzen ist der entscheidende Unterschied zu einem einfachen LIKE '%alt-klasse%': Ohne ihn wird auch alt-klasse-2 oder meine-alt-klasse getroffen – und genau daraus entstehen die Fehler, die erst Wochen später auffallen.

Schritt 3: Ersetzen – mit exakter Wortgrenze

-- MySQL 8 / MariaDB 10.5+: REGEXP_REPLACE
UPDATE tl_content
SET cssID = REGEXP_REPLACE(
      cssID,
      '(^|[^a-z0-9_-])alt-klasse([^a-z0-9_-]|$)',
      '\\1neue-klasse\\2'
    )
WHERE cssID REGEXP '(^|[^a-z0-9_-])alt-klasse([^a-z0-9_-]|$)';

-- Ältere Versionen ohne REGEXP_REPLACE: gezielt und mit Leerzeichen-Puffer
UPDATE tl_content
SET cssID = TRIM(REPLACE(CONCAT(' ', cssID, ' '), ' alt-klasse ', ' neue-klasse '))
WHERE CONCAT(' ', cssID, ' ') LIKE '% alt-klasse %';

Schritt 4: Nachkontrolle

-- Darf keine Treffer mehr liefern
SELECT COUNT(*) FROM tl_content
WHERE cssID REGEXP '(^|[^a-z0-9_-])alt-klasse([^a-z0-9_-]|$)';

-- Cache leeren, sonst zeigt das Frontend die alte Fassung
-- vendor/bin/contao-console cache:clear --env=prod

Nicht vergessen: Klassen stecken nicht nur in tl_content

  • tl_article, tl_module und tl_layout führen ebenfalls CSS-Klassen.
  • In Rich-Text-Feldern (tl_content.text) stehen Klassen mitten im HTML – dort hilft nur eine Suche mit Kontext, kein pauschales Ersetzen.
  • Templates und Stylesheets im Dateisystem sind von der Datenbank unberührt: grep -rl "alt-klasse" templates/ files/ zeigt, was noch offen ist.

Wer den Umbau in einem größeren Projekt macht, schreibt ihn besser als Contao-Migration – dann läuft er auf Staging und Live identisch, ist versioniert und lässt sich nachvollziehen. Ein Datenbank-Einzeiler auf der Live-Umgebung ist der schnellste Weg zu einem unerklärlichen Layoutfehler drei Wochen später.

Fazit

Die Datenbank ist beim Klassen-Umbau der Ort, an dem sich Gründlichkeit auszahlt – und Nachlässigkeit rächt. Ein automatisiertes Skript erledigt in Sekunden, was manuell Tage kostet; entscheidend ist die Reihenfolge: Backup, Trockenlauf, Ersetzung mit Wortgrenzen, Nachkontrolle. Wer diese vier Schritte einhält, kann dem UPDATE-Befehl gelassen zusehen.

Und wer den Umbau öfter als einmal braucht – Staging, Live, das nächste Projekt –, gießt ihn in eine Contao-Migration. Dann ist die Ersetzung kein riskanter Handgriff mehr, sondern ein dokumentierter, wiederholbarer Teil des Deployments.

Zurück zur Blog-Übersicht