15%

15% auf alle Hosting-Dienste sparen

Teste deine Fähigkeiten und erhalte Rabatt auf jeden Hosting-Plan

Benutze den Code:

Skills
Anfangen
21.10.2024

WordPress Hooks erklärt: Actions, Filter und fortgeschrittene Verwendungsmuster

WordPress-Hooks sind ein zentraler Architekturmechanismus, der es Entwicklern ermöglicht, benutzerdefinierten Code in vordefinierte Ausführungspunkte innerhalb von WordPress einzufügen – ohne Core-Dateien, Themes oder Drittanbieter-Plugins zu modifizieren. Es gibt genau zwei Typen: Action-Hooks, die benutzerdefinierte Funktionen bei bestimmten Ereignissen auslösen, und Filter-Hooks, die Daten abfangen und transformieren, bevor sie gerendert oder gespeichert werden. Die Beherrschung beider Typen ist für jede ernsthafte WordPress-Entwicklungsarbeit unerlässlich.

Dieser Leitfaden geht über die Grundlagen hinaus. Sie finden präzise Syntaxreferenzen, praxisnahe Sonderfälle, Prioritätsmechaniken und Architekturmuster, die wartbaren WordPress-Code von fehleranfälligem, konfliktträchtigem Code unterscheiden.

Das WordPress-Hook-System: Wie es unter der Haube funktioniert

WordPress wird in einer vorhersehbaren Reihenfolge ausgeführt – Bootstrapping des Cores, Laden von Plugins, Laden des aktiven Themes und anschließendes Rendern der angeforderten Seite. Während dieses gesamten Lebenszyklus ruft die Engine do_action() und apply_filters() an Hunderten von vordefinierten Punkten auf. Diese Aufrufe sind die Hooks.

Wenn Sie einen Callback mit add_action() oder add_filter() registrieren, speichert WordPress ihn in einem globalen $wp_filter-Array, das nach Hook-Name und Priorität geordnet ist. Zur Laufzeit, wenn der Hook ausgelöst wird, iteriert WordPress durch jeden registrierten Callback in Prioritätsreihenfolge und führt sie sequenziell aus.

Diese Architektur bedeutet:

  • Sie berühren niemals WordPress-Core-Dateien (wp-includes/, wp-admin/)
  • Ihre Anpassungen überstehen Core-Updates unbeschadet
  • Mehrere Plugins können sich an denselben Hook anhängen, ohne Konflikte zu verursachen – vorausgesetzt, die Prioritäten werden korrekt verwaltet

Alle Hook-Registrierungen sollten in einem benutzerdefinierten Plugin oder in der functions.php Ihres Themes gespeichert sein. Für Produktionsumgebungen, die auf einem VPS Hosting-Plan betrieben werden, wird die Bereitstellung von Anpassungen als eigenständiges Plugin gegenüber functions.php ausdrücklich bevorzugt, da ein Theme-Wechsel Ihre Funktionalität nicht stillschweigend löscht.

Action-Hooks vs. Filter-Hooks: Grundlegende Unterschiede

AttributAction-HooksFilter-Hooks
HauptzweckNebeneffekte bei einem bestimmten Ereignis ausführenDaten abfangen und transformieren
Rückgabewert erforderlichNein – Callbacks geben nichts zurückJa – Callbacks MÜSSEN einen Wert zurückgeben
Core-Funktion zum Auslösen`do_action()``apply_filters()`
Core-Funktion zur Registrierung`add_action()``add_filter()`
Entfernungsfunktion`remove_action()``remove_filter()`
Typische AnwendungsfälleScripts einbinden, E-Mails senden, Ereignisse protokollierenInhalte ändern, Titel anpassen, Query-Argumente transformieren
An Callback übergebene DatenOptionale kontextbezogene ArgumenteDer zu filternde Datenwert (erforderlich)
VerkettungsverhaltenCallbacks werden sequenziell und unabhängig voneinander ausgeführtJeder Callback erhält die Ausgabe des vorherigen

Der häufigste Fehler, den Entwickler machen, ist das Vergessen, einen Wert innerhalb eines Filter-Callbacks mit return zurückzugeben. Wenn Sie die Return-Anweisung weglassen, wird der gefilterte Wert zu null, was die Ausgabe im Frontend stillschweigend unterbricht – ein notorisch schwer zu verfolgender Fehler.

Action-Hooks: Tiefgehende Analyse

Syntax und Parameter

add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 );
  • $hook_name — Der genaue Name des Hooks, an den angehängt werden soll.
  • $callback — Jedes gültige PHP-Callable: eine benannte Funktion, eine anonyme Funktion, eine statische Methode (['ClassName', 'method']) oder eine Objektmethode ([$object, 'method']).
  • $priority — Ausführungsreihenfolge relativ zu anderen Callbacks am selben Hook. Niedrigere Zahlen werden zuerst ausgeführt. Standard ist 10. Verwenden Sie negative Ganzzahlen, um vor allen Standard-Callbacks ausgeführt zu werden.
  • $accepted_args — Wie viele Argumente Ihr Callback vom Hook akzeptiert. Muss mit dem übereinstimmen, was do_action() übergibt, sonst erhalten Sie eine PHP-Warnung.

Grundlegendes Beispiel: Inhalt nach jedem Beitrag anhängen

add_action( 'the_content', 'alexhost_append_cta', 20 );

function alexhost_append_cta( $content ) {
    if ( is_single() && in_the_loop() && is_main_query() ) {
        $content .= '<p class="post-cta">Enjoyed this article? Share it with your network.</p>';
    }
    return $content;
}

Beachten Sie die in_the_loop()– und is_main_query()-Prüfungen. Ohne sie wird Ihr Callback bei jedem Aufruf von the_content() ausgelöst – einschließlich Widget-Bereichen, Page-Buildern und REST-API-Antworten – was zu doppelter Ausgabe führt, die extrem schwer zu debuggen ist.

Erweitertes Beispiel: Slack-Benachrichtigung beim Veröffentlichen eines Beitrags senden

add_action( 'transition_post_status', 'alexhost_notify_on_publish', 10, 3 );

function alexhost_notify_on_publish( $new_status, $old_status, $post ) {
    if ( 'publish' === $new_status && 'publish' !== $old_status && 'post' === $post->post_type ) {
        $webhook_url = defined( 'SLACK_WEBHOOK_URL' ) ? SLACK_WEBHOOK_URL : '';
        if ( empty( $webhook_url ) ) {
            return;
        }
        wp_remote_post( $webhook_url, [
            'body'        => wp_json_encode( [ 'text' => 'New post published: ' . get_permalink( $post ) ] ),
            'headers'     => [ 'Content-Type' => 'application/json' ],
            'data_format' => 'body',
        ] );
    }
}

Dieses Muster verwendet transition_post_status anstelle von publish_post, da es Ihnen sowohl den alten als auch den neuen Status liefert und es Ihnen ermöglicht, eine erstmalige Veröffentlichung von einer Aktualisierung eines bereits veröffentlichten Beitrags zu unterscheiden.

Eine von einem anderen Plugin registrierte Action entfernen

remove_action( 'wp_footer', 'some_plugin_footer_function', 10 );

Der Prioritätswert in remove_action() muss genau mit der Priorität übereinstimmen, die im ursprünglichen add_action()-Aufruf verwendet wurde. Wenn Sie die Priorität nicht kennen, überprüfen Sie den Quellcode des Plugins oder verwenden Sie ein Hook-Debugging-Tool. Eine Nichtübereinstimmung bedeutet, dass die Entfernung stillschweigend fehlschlägt – die Funktion wird weiterhin ausgeführt.

Filter-Hooks: Tiefgehende Analyse

Syntax und Parameter

add_filter( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 );

Die Signatur ist identisch mit add_action(). Der entscheidende Verhaltensunterschied: Ihr Callback erhält den aktuellen Wert der gefilterten Daten als erstes Argument und muss einen Wert zurückgeben.

Grundlegendes Beispiel: Beitragstitel in Title Case umwandeln

add_filter( 'the_title', 'alexhost_titlecase_post_title', 10, 2 );

function alexhost_titlecase_post_title( $title, $post_id ) {
    if ( is_admin() ) {
        return $title;
    }
    return mb_convert_case( $title, MB_CASE_TITLE, 'UTF-8' );
}

Die Verwendung von mb_convert_case() anstelle von strtoupper() ist der richtige Ansatz für mehrsprachige Websites. strtoupper() ist nicht multibyte-sicher und beschädigt Zeichen in nicht-lateinischen Schriften.

Erweitertes Beispiel: Argumente der Hauptabfrage ändern

add_filter( 'pre_get_posts', 'alexhost_exclude_category_from_home' );

function alexhost_exclude_category_from_home( $query ) {
    if ( ! is_admin() && $query->is_main_query() && $query->is_home() ) {
        $query->set( 'category__not_in', [ 5, 12 ] );
    }
}

pre_get_posts ist technisch gesehen ein Action-Hook (er erfordert keine Rückgabe), modifiziert jedoch das WP_Query-Objekt per Referenz – wodurch er sich wie ein Filter verhält. Dies ist ein häufiger Punkt der Verwirrung. Sie modifizieren $query direkt; Sie geben es nicht zurück.

Filter verketten: Was Entwickler übersehen

Wenn mehrere Callbacks an denselben Filter angehängt werden, erhält jeder die Ausgabe des vorherigen. Wenn Callback A bei Priorität 10 $content transformiert und Callback B bei Priorität 11 ebenfalls $content transformiert, arbeitet B auf der Ausgabe von A – nicht auf dem Original. Diese Verkettung ist leistungsstark, erfordert jedoch eine bewusste Prioritätsplanung, wenn mehrere Plugins dieselben Daten verarbeiten.

Priorität und Ausführungsreihenfolge: Eine praktische Referenz

PrioritätswertWann er ausgeführt wirdTypischer Anwendungsfall
`1` – `9`Vor WordPress-StandardsCore-Verhalten frühzeitig überschreiben
`10`StandardStandard-Plugin-/Theme-Anpassungen
`11` – `19`Nach Standard, vor späten HooksAusgabe eines anderen Plugins nachbearbeiten
`20` – `99`Späte AusführungBereinigung, abschließende Formatierung
`PHP_INT_MAX`Absolut letzterGarantierte Ausführung als letztes Mittel
Negativ (z. B. `-1`)Vor allem anderenVorinitialisierungsaufgaben

Referenz der wichtigsten WordPress-Hooks

Besonders wertvolle Action-Hooks

  • init — Wird ausgelöst, nachdem WordPress geladen wurde, aber bevor Header gesendet werden. Verwenden Sie ihn zur Registrierung benutzerdefinierter Beitragstypen, Taxonomien und Rewrite-Regeln. Vermeiden Sie die Verwendung von plugins_loaded für die CPT-Registrierung – er wird zu früh ausgelöst.
  • wp_enqueue_scripts — Der einzig korrekte Ort zum Einbinden von Frontend-CSS und JavaScript. Verwenden Sie niemals wp_head direkt für die Script-Einbindung.
  • admin_enqueue_scripts — Assets ausschließlich im Admin-Dashboard einbinden. Akzeptiert ein $hook_suffix-Argument zur Ausrichtung auf bestimmte Admin-Seiten.
  • wp_footer — Wird kurz vor </body> ausgelöst. Ideal für Analytics-Snippets, verzögerte Scripts und nicht-kritisches Markup.
  • save_post — Wird ausgelöst, nachdem ein Beitrag gespeichert wurde. Verwenden Sie ihn zum Auslösen der Cache-Invalidierung, zur Datensynchronisierung mit externen APIs oder zur Aktualisierung benutzerdefinierter Meta-Daten. Überprüfen Sie immer die Nonce und prüfen Sie wp_is_post_revision(), um doppeltes Auslösen zu vermeiden.
  • template_redirect — Wird ausgelöst, bevor WordPress bestimmt, welches Template geladen werden soll. Verwenden Sie ihn für benutzerdefinierte Weiterleitungen oder Zugriffskontrollen.
  • wp_login — Wird bei erfolgreicher Benutzeranmeldung ausgelöst. Nützlich für Audit-Protokollierung oder Sitzungsverwaltung auf Mehrbenutzer-Websites.

Besonders wertvolle Filter-Hooks

  • the_content — Filtert Beitragsinhalt vor der Anzeige. Beachten Sie: Dieser Hook wird bei jedem get_the_content()-Aufruf ausgelöst, einschließlich REST-API-Antworten in WordPress 5.5+.
  • the_title — Filtert Beitrags- und Seitentitel. Erhält sowohl $title als auch $post_id als Argumente, wenn $accepted_args auf 2 gesetzt ist.
  • excerpt_length — Steuert die Wortanzahl von automatisch generierten Auszügen. Gibt eine Ganzzahl zurück.
  • upload_mimes — Filtert die Liste der erlaubten Upload-MIME-Typen. Verwenden Sie dies, um SVG-Uploads (mit ordnungsgemäßer Bereinigung) zu ermöglichen oder Uploads auf bestimmte Dateitypen zu beschränken.
  • wp_nav_menu_items — Filtert die HTML-Ausgabe von Navigationsmenüs. Nützlich zum Einfügen dynamischer Elemente wie Anmelde-/Abmeldelinks.
  • body_class — Filtert das Array der CSS-Klassen, die auf das <body>-Tag angewendet werden. Akzeptiert ein Array, keinen String – eine häufige Fehlerquelle.
  • cron_schedules — Fügt benutzerdefinierte WP-Cron-Intervalle hinzu. Unverzichtbar für Hintergrundverarbeitungsaufgaben auf Websites, die auf Dedicated Servers gehostet werden, wo Sie auch einen echten System-Cron als Ersatz konfigurieren können.

Benutzerdefinierte Hooks in Plugins und Themes erstellen

Gut strukturierte Plugins stellen eigene Hooks bereit, damit andere Entwickler sie erweitern können, ohne den Code zu forken. Dies ist das Merkmal professioneller WordPress-Entwicklung.

Einen benutzerdefinierten Action-Hook definieren

// Inside your plugin's core function
function alexhost_process_order( $order_id ) {
    // ... processing logic ...

    // Fire a custom action so other code can react
    do_action( 'alexhost_order_processed', $order_id );
}

Einen benutzerdefinierten Filter-Hook definieren

function alexhost_get_product_price( $product_id ) {
    $base_price = get_post_meta( $product_id, '_price', true );

    // Allow other code to modify the price before returning it
    return apply_filters( 'alexhost_product_price', $base_price, $product_id );
}

Jedes Plugin oder Theme kann sich nun in alexhost_product_price einhängen, um Rabatte, Währungsumrechnungen oder Steuerberechnungen anzuwenden – ohne den Quellcode Ihres Plugins zu berühren.

Hooks entfernen und ersetzen: Fortgeschrittene Muster

Einen innerhalb einer Klasse registrierten Hook entfernen

Dies ist einer der am häufigsten missverstandenen Aspekte des Hook-Systems. Wenn ein Plugin eine Methode über eine Objektinstanz registriert, können Sie sie nicht mit einer einfachen String-Referenz entfernen.

// Plugin registers like this:
$plugin_instance = new SomePlugin();
add_action( 'init', [ $plugin_instance, 'setup' ] );

// To remove it, you need access to the same object instance.
// One approach: hook into plugins_loaded and use the global instance if exposed.
add_action( 'plugins_loaded', function() {
    global $some_plugin;
    if ( isset( $some_plugin ) && is_a( $some_plugin, 'SomePlugin' ) ) {
        remove_action( 'init', [ $some_plugin, 'setup' ] );
    }
}, 20 );

Wenn das Plugin seine Instanz nicht global bereitstellt, müssen Sie $GLOBALS['wp_filter'] direkt iterieren – ein fragiler Ansatz, der darauf hindeutet, dass das Ziel-Plugin eine schlechte Architektur hat.

has_action() und has_filter() defensiv verwenden

if ( has_action( 'wp_footer', 'some_third_party_function' ) ) {
    remove_action( 'wp_footer', 'some_third_party_function' );
}

has_action() gibt die Priorität des registrierten Callbacks (eine Ganzzahl) zurück, wenn gefunden, oder false, wenn nicht. Dieser Rückgabewert wird häufig falsch verwendet – Entwickler prüfen if ( has_action(...) ) und erwarten einen booleschen Wert, aber der Empfang von 0 (eine gültige Priorität) wird als falsy ausgewertet. Verwenden Sie immer !== false für eine zuverlässige Prüfung:

if ( false !== has_action( 'wp_footer', 'some_third_party_function' ) ) {
    remove_action( 'wp_footer', 'some_third_party_function', 0 );
}

Leistungsüberlegungen für Produktionsumgebungen

Hooks verursachen individuell minimalen Overhead, aber schlecht geschriebene Callbacks summieren sich zu messbarer Latenz. Wichtige Muster, die zu befolgen sind:

  • Teure Operationen mit Bedingungen absichern. Datenbankabfragen, Remote-API-Aufrufe und Datei-I/O innerhalb von Hook-Callbacks müssen in bedingte Prüfungen eingebettet werden (is_single(), is_admin(), is_main_query()), um zu verhindern, dass sie bei jedem Seitenaufruf ausgeführt werden.
  • Objekt-Caching verwenden. Wenn ein Hook-Callback Daten aus der Datenbank abruft, verpacken Sie das Ergebnis in einem Transient oder verwenden Sie wp_cache_get() / wp_cache_set(). Auf einem ordnungsgemäß konfigurierten VPS mit cPanel oder einem Server mit Redis reduziert dies Datenbankzugriffe erheblich.
  • Anonyme Funktionen vermeiden, wenn Sie Hooks entfernen müssen. Sie können remove_action() nicht auf eine anonyme Funktion anwenden, da Sie keine Referenz darauf haben. Verwenden Sie immer benannte Funktionen oder gespeicherte Referenzen für Callbacks, die Sie möglicherweise deregistrieren müssen.
  • Hook-Last mit Query Monitor prüfen. Das Query Monitor-Plugin bietet ein dediziertes „Hooks & Actions”-Panel, das jeden während einer Anfrage ausgelösten Hook, die angehängten Callbacks und deren Ausführungszeit anzeigt. Dies ist unverzichtbar für die Diagnose von Leistungsregressionen auf stark frequentierten Websites.

Sicherheitsüberlegungen

Hooks sind eine häufige Angriffsfläche in schlecht geschriebenen Plugins. Spezifische Risiken, die zu verstehen sind:

  • Nicht validierte Eingaben in save_post-Callbacks. Überprüfen Sie immer die Nonce (check_admin_referer()), bestätigen Sie current_user_can() und bereinigen Sie alle $_POST-Daten vor der Verarbeitung.
  • Privilegienerweiterung über init-Hooks. Code, der Benutzerrollen oder -berechtigungen innerhalb von init ohne Berechtigungsprüfung ändert, kann durch nicht authentifizierte Anfragen ausgelöst werden.
  • Filter-Injection. Wenn ein Filter-Callback Daten direkt ohne Escaping auf der Seite ausgibt, wird er zu einem XSS-Angriffsvektor. Filter sollten Daten transformieren; das Escaping sollte am Ausgabepunkt mit esc_html(), esc_attr() oder wp_kses_post() erfolgen.
  • SSRF über hook-ausgelöste HTTP-Anfragen. Callbacks, die wp_remote_get()-Aufrufe basierend auf benutzerdefinierten URLs durchführen (z. B. in save_post), müssen die URL mit esc_url_raw() validieren und bereinigen und idealerweise erlaubte Hosts einschränken.

Für Websites, die sensible Daten oder E-Commerce-Transaktionen verarbeiten, ist die Kombination Ihrer WordPress-Installation mit einem ordnungsgemäß konfigurierten SSL-Zertifikate-Setup eine grundlegende Anforderung – Hooks, die Daten über unverschlüsselte Verbindungen an externe Endpunkte übertragen, stellen eine kritische Sicherheitslücke dar.

Checkliste für bewährte Praktiken

  • Verwenden Sie eindeutige, namespaced Funktionsnamen (z. B. myplugin_functionname), um Kollisionen mit Core, Themes und anderen Plugins zu vermeiden.
  • Geben Sie immer $accepted_args an, wenn Ihr Callback mehr als ein Argument vom Hook benötigt.
  • Verwenden Sie niemals echo innerhalb eines Filter-Callbacks – nur return.
  • Platzieren Sie Hook-Registrierungen innerhalb einer bedingten Prüfung oder Initialisierungsfunktion, nicht im globalen Gültigkeitsbereich einer Datei, die möglicherweise mehrfach eingebunden wird.
  • Dokumentieren Sie jeden benutzerdefinierten Hook, den Sie mit @hook-Docblocks bereitstellen, damit andere Entwickler sie entdecken können.
  • Testen Sie das Entfernen von Hooks mit exakter Prioritätsübereinstimmung – eine Nichtübereinstimmung ist ein stilles Versagen.
  • Verwenden Sie current_filter() innerhalb eines Callbacks, um zu bestätigen, welcher Hook ihn ausgelöst hat, wenn eine einzelne Funktion an mehrere Hooks angehängt ist.

Praktische Entscheidungsmatrix: Wann welcher Hook-Typ verwendet werden sollte

SzenarioHook-TypEmpfohlener Hook
Tracking-Pixel vor `</body>` hinzufügenAction`wp_footer`
Beitragsinhalt vor der Anzeige ändernFilter`the_content`
Einen benutzerdefinierten Beitragstyp registrierenAction`init`
Datei-Upload-Typen einschränkenFilter`upload_mimes`
E-Mail senden, wenn Bestellung abgeschlossen istActionBenutzerdefinierte Action in der Bestellverarbeitungsfunktion
Wortanzahl des Auszugs ändernFilter`excerpt_length`
Nicht angemeldete Benutzer weiterleitenAction`template_redirect`
CSS-Klasse zum Body-Tag hinzufügenFilter`body_class`
Ein benutzerdefiniertes Stylesheet einbindenAction`wp_enqueue_scripts`
WP_Query vor der Ausführung ändernAction (per Referenz)`pre_get_posts`

FAQ

Was ist der Unterschied zwischen do_action() und apply_filters() in WordPress?

do_action() löst einen Action-Hook aus – es führt alle registrierten Callbacks an diesem Punkt aus, gibt jedoch keinen Rückgabewert an den aufrufenden Code zurück. apply_filters() löst einen Filter-Hook aus – es übergibt einen Wert sequenziell durch alle registrierten Callbacks und gibt den endgültig transformierten Wert an den Aufrufer zurück. Actions erzeugen Nebeneffekte; Filter transformieren Daten.

Kann ein WordPress-Filter-Hook als Action-Hook verwendet werden?

Technisch gesehen ist add_action() ein Wrapper um add_filter() im WordPress-Core. Die Verwendung eines Filter-Hooks als Action (ohne Rückgabe eines Wertes) führt jedoch dazu, dass der gefilterte Wert zu null wird, was die verarbeiteten Daten beschädigt. Verwenden Sie immer die semantisch korrekte Funktion für den beabsichtigten Zweck.

Warum schlägt remove_action() manchmal fehl, einen Hook zu entfernen?

Die häufigste Ursache ist eine Prioritätsnichtübereinstimmung – die an remove_action() übergebene Priorität muss genau mit der Priorität übereinstimmen, die im ursprünglichen add_action()-Aufruf verwendet wurde. Die zweithäufigste Ursache ist das Timing: remove_action() muss aufgerufen werden, nachdem der Hook registriert wurde, aber bevor er ausgelöst wird. Wenn die ursprüngliche Registrierung innerhalb eines Klassenkonstruktors oder eines spät auslösenden Hooks erfolgt, wird Ihr Entfernungsaufruf möglicherweise zu früh ausgeführt.

Was ist der sicherste Ort, um benutzerdefinierte WordPress-Hooks in einer Produktionsumgebung hinzuzufügen?

Ein eigenständiges, zweckgebautes Plugin ist der sicherste Ort. Im Gegensatz zu functions.php bleibt ein Plugin über Theme-Wechsel hinweg bestehen und lässt sich leichter versionieren, testen und unabhängig bereitstellen. In verwalteten VPS Hosting-Umgebungen ist die Speicherung benutzerdefinierter Plugins in einem privaten Git-Repository und die Bereitstellung über CI/CD-Pipelines der produktionsreife Standard.

Wie debugge ich, welche Hooks auf einer bestimmten WordPress-Seite ausgelöst werden?

Installieren Sie das Query Monitor-Plugin und navigieren Sie zur Zielseite, während Sie als Administrator angemeldet sind. Der Tab „Hooks & Actions” listet jeden ausgelösten Hook, jeden angehängten Callback und die Ausführungszeit pro Callback auf. Für CLI-basiertes Debugging auf einem Server bietet wp hook list --format=table über WP-CLI ein statisches Inventar aller registrierten Hooks, ohne einen Browser zu laden.

15%

15% auf alle Hosting-Dienste sparen

Teste deine Fähigkeiten und erhalte Rabatt auf jeden Hosting-Plan

Benutze den Code:

Skills
Anfangen