Embeds in WordPress filtern – aber wie genau?

Diese Woche hat Walter Ebert einen spannenden Tweet getwittert. Es ging um einen Codeschnipsel mit dem in WordPress die YouTube-URL geändert wurde auf die „ohne Cookies“-Variante. Johannes Kinast empfahl einen geeigneteren Filter, der performanter sein soll und ich fragte mich, wann genau das eigentlich gecached wird und wann welcher Filter eigentlich feuert. Ralf Wiechers hat dann noch einen weiteren Filter ausgegraben. Schauen wir uns dieses Wirwarr mal an und versuchen daraus schlau zu werden. Aber ohne Gewähr, denn ich schaue mir das Ganze beim Schreiben an. Wenn ich am Artikel-Ende keine Lösung habe, müsst ihr mir in den Kommentaren helfen … 😉

Starten wir mit Walters Snippet, der den Anstoß geliefert hat:

/*
Use YouTube privacy-enhanced mode. More info: https://support.google.com/youtube/answer/171780?visit_id=637419781721286051-946666784&rd=1
*/
add_filter(
	'the_content',
	function( $content ) {
		return str_replace(
			' src="https://www.youtube.com/embed/',
			' src="https://www.youtube-nocookie.com/embed/',
			$content
		);
	},
	11
);

Der genutzte Filter ist hier the_content. Laut Doku gilt für den Filter:

This filter is used to filter the content of a post after it is retrieved from the database and before it is printed to the screen.

Er feuert also jedes Mal, wenn der Artikel ausgegeben wird (ein mögliches Cache-Plugin ignorieren wir für die Betrachtung) mal. Daher vermutlich der Einwand, dass es performanter gehen müsste. Und zwar mit embed_oembed_html. Für diesen Filter gibt es nur eine sehr kurze Beschreibung:

Filters the cached oEmbed HTML.

Er filtert also nur einen Cache. Soso … dieser Cache muss ja auch mal generiert werden. Suchen wir also weiter. Da gibt es noch oembed_dataparse und oembed_result.

Wenn WordPress einen einbettungsfähigen Link im Inhalt findet, dann wird das vom oEmbed-Provider gelieferte HTML zurückgegeben. oembed_dataparse filtert das zurückgelieferte HTML als erstes und passt das HTML je nach Typ gegebenenfalls etwas an. oembed_result filtert dann direkt danach. Das Ergebnis wird gecached.

Daher müsste the_content tatsächlich am längsten dauern. Der Filter kommt erst ganz am Ende zum Einsatz und filtert bei jedem Aufruf das gesamte HTML des Inhalts. embed_oembed_html filtert zwar nur noch das gecachte HTML des oEmbed-Providers, aber eben auch jedes Mal. Nur die beiden letzten Kandidaten oembed_dataparse und oembed_result filtern bereits vor dem Cache und dürften so die performantesten sein. Siehe auch diese Diskussion auf WordPress Stackexchange.

Aber (!) mit einem kleinen Haken: Wenn der Filter erst aktiviert wird, nachdem der Cache bereits angelegt wurde, dann passiert gar nichts. Damit der Filter auch für bereits bestehende Artikel/Seiten greift, müssten die Postmeta-Informationen gelöscht werden.

Unser neuer Snippet (Ralfs version) sieht jetzt also so aus:

/*
 * Remove Cookies from YouTube Videos
 */
add_filter( 'oembed_result', 'sm_youtube_nocookie_domain', 10, 3 );
function sm_youtube_nocookie_domain($html, $url, $args) {
    if ( preg_match('#https?://(www\.)?youtu#i', $url) ) {
        return preg_replace(
            '#src=(["\'])(https?:)?//(www\.)?youtube\.com#i',
            'src=$1$2//$3youtube-nocookie.com',
            $html
        );
    }
    return $html;
}

Da fiel mir ein, dass Embed Privacy doch mit dem gleichen Problem zu tun gehabt haben müsste. Schauen wir doch mal neugierig in den Quelltext:

Sie nutzen den Filter embed_oembed_html:

add_filter( 'embed_oembed_html', [ $this, 'replace_embeds' ], 10, 3 );

Ändern ebenfalls die URL (und geben dann aber ein Template zum Nachladen aus):

// replace youtube.com to youtube-nocookie.com
$output = str_replace( 'youtube.com', 'youtube-nocookie.com', $output );

Nur ein paar Sachen verwundern mich. Bei Aktivierung und Deaktivierung wird die Funktion clear_embed_cache aufgerufen, mit foldender Erklärung im DocBlock:

/**
 * Embeds are cached in the postmeta database table and need to be removed
 * whenever the plugin will be enabled or disabled.
 */

Nach meinem Verständnis filtert doch embed_oembed_html das gecachte Ergebnis!? Ein Löschen der Caches wäre daher gar nicht nötig. Das wäre nur bei oembed_dataparse und oembed_result notwendig.

Und wieso werden die Postmeta-Daten mit einem manuellen Query gelöscht?

// the query to delete cache
$query = "DELETE FROM		" . $wpdb->get_blog_prefix() . "postmeta
WHERE				meta_key LIKE '%_oembed_%'";

Und nicht mit delete_post_meta? Und warum hat WordPress genau dafür, zum Löschen aller oEmbed-Caches eine Funktion, nutzt sie aber nicht mehr selbst?

Und noch einen anderen Gedanken hatte ich: Wenn der oEmbed-Provider nicht angefragt werden soll, dann müssten sie doch eigentlich den Filter pre_oembed_result nutzen, weil sonst der http-Request ja doch gemacht wird (oder ist das nur auf Serverebene und daher irrelevant)?

***

Vielleicht konnte ich ein bisschen zum Quelltext lesen anregen und vielleicht hat ja auch jemand Lust hier mitzumachen und weiter zu fachsimpeln. Stimmt das so? Habe ich was übersehen?

Ich freue mich über Gegenrede, Lob & Kritik und über Kekse (wieso komme ich gerade auf Cookies?). Falls also jemand keine Fachmeinung, aber Weihnachtskekse hat, dann gerne auch die schicken 😉

2 Antworten auf Embeds in WordPress filtern – aber wie genau?

  1. Zuerst einmal, warum wir `embed_oembed_html` verwenden: Filtern wir bereits vorher, wird auch die Funktionalität im Backend beeinträchtigt. Da wir dort aber Embed Privacy explizit nicht laden wollen, genauso wenig in Gutenberg bereits den umgewandelten Code dastehen haben wollen (dieser steht dann nämlich bei einer Deaktivierung/Deinstallation des Plugins immer noch da und macht dann das Embed kaputt), haben wir uns dazu entschieden, die tatsächlich performancetechnisch schlechtere Lösung zu wählen, gleichzeitig aber die funktional für uns am besten geeignete.

    Dann, warum wir `clear_embed_cache` aufrufen: Frag mich bitte nicht wo, ist schon eine Weile her, dass ich den Teil entwickelt habe, wird der von Embed Privacy abgeänderte Code noch in der Datenbank als Post-Meta zwischengespeichert. Da diese Daten nach der Deaktivierung/Deinstallation aber veraltet sind, werden sie gelöscht.

    Und warum machen wir das per eigener Query? Weil `delete_post_meta` nur für einen einzelnen Post geht. Da ist es wesentlich performanter, eine Query für eine Entfernung zu nutzen, als sich erst per `get_posts` alle Beiträge aller Beitragstypen zu holen, denn tendenziell kann dort überall ein Embed verwendet werden, um dann wieder einzeln Beitrag für Beitrag dessen Post-Meta zu löschen.

    Zu deinem weiteren Gedanken: Zumindest wir wollen explizit die Funktion des Embeds erhalten. Embed Privacy ist dahingehend nur im Frontend für Seitenbesucher aktiv. Die Generierung und Darstellung des Embeds ist nach wie vor wie gehabt und daher auch mit einem Abruf bei dem jeweiligen oEmbed-Anbieter verbunden. Daher sind wir auch auf diese Anfrage und das entsprechende Ergebnis angewiesen, da wir um diesen Code lediglich noch zusätzliche Funktionalität in Form von einem veränderten Markup einbauen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.