Blog

Code Snippet: DSGVO-konforme YouTube Videos

Version: 1.2.0 (25.09.2022)

Inhalt

Einführung

Auf vielen Websites werden YouTube Videos eingebunden.

Nutzt man hierzu die in WordPress (Classic Editor, Gutenberg) oder Page Buildern (Oxygen, und viele andere) vorgesehenen Standard-Verfahren und fragt nicht vorher die Zustimmung des Besuchers ab, verstößt man in der EU gegen die Datenschutzrichtlinien (DSGVO, GDPR): Bei der Anzeige einer Seite, die ein YouTube Video enthält, wird automatisch eine Verbindung zu den YouTube Servern aufgebaut, um das Vorschaubild zu laden. Dabei wird die IP-Adresse des Besuchers, und damit eine personenbezogene Information, an YouTube übertragen. Das ist nicht erlaubt ohne explizite Zustimmung.

Wie kann man also YouTube Videos auf der Website DSGVO-konform einbinden?

Lösung

Das hier beschriebene Code Snippet stellt einen WordPress Shortcode zur Verfügung, der eine DSGVO-konforme und gleichzeitig möglichst einfache und effiziente Einbettung von YouTube Videos ermöglicht.

Mittels dieses Shortcodes wird das Vorschaubild für das Video bereits durch den Server von YouTube abgerufen und lokal zwischengespeichert. Dem Browser des Besuchers wird dieses lokale Vorschaubild übergeben, zusammen mit einem Hinweistext zu den Datenschutzrichtlinien. Der Browser braucht also zunächst keine Verbindung zu YouTube aufzubauen. Erst nach Klick durch den Besucher wird das eigentliche Video von YouTube geladen. Dieses Verfahren ist DSGVO-konform, weil der Besucher zunächst aktiv bestätigen muss, dass er das Video von YouTube laden will.

Das Code Snippet verwendet für den Abruf des Videos außerdem die spezielle Domain youtube-nocookie.com und vermeidet so die Verwendung von Cookies seitens YouTube.

Und so ganz nebenbei sollte sich diese Lösung auch deutlich positiv auf die bekannten Speed Test Tools (Page Speed Insights, GTMetrix, ...) auswirken, da keine Daten von externen Servern geladen werden.

Shortcode

Die einfachste Syntax für den Shortcode lautet:

[ma-gdpr-youtube id="4jlOF09WRw8"]

wobei 4jlOF09WRw8 hier für die ID des YouTube Videos steht. Die ID kann man sich ganz einfach aus dem URL zu dem YouTube Video kopieren:

Dieser Shortcode kann ganz einfach direkt im Classic oder Gutenberg Editor geschrieben werden.
In Page Buildern steht dafür meist ein eigenes Element "Shortcode" zur Verfügung.

Dieser Shortcode erzeugt folgende Ausgabe:

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

Das Vorschaubild wird nicht von YouTube, sondern aus dem lokalen Zwischenspeicher geladen.
Mittig wird das Vorschaubild durch den von YouTube bekannten Play-Button überlagert.
Und unten sieht der Besucher in einem farblich abgehobenen Balken einen Hinweistext zum Datenschutz.

Vorschaubild

Der Shortcode lädt automatisch das Vorschaubild für dieses Video von YouTube.

Formate

Es werden verschiedene Bildformate abgerufen:

Format-NameSeiten­verhältnisGröße
mqdefault16:9320 x 180 px
hqdefault4:3480 x 360 px
sddefault4:3640 x 480 px
hq72016:91280 x 720 px
maxresdefault16:9Originalgröße, z.B. 1920 x 1080 px

Die Bildformate dienen der optimalen Darstellung des Vorschaubildes in verschiedenen Bildschirm-Auflösungen.
Das Snippet verwendet schon bei der Hälfte der nächst höheren Auflösung das höher aufgelöste Vorschaubild, um die Anzeigequalität zu verbessern.

Hinweise:

  • Obwohl YouTube zwar in der Dokumentation schreibt, die oben genannten Formate stünden immer zur Verfügung, fehlen vereinzelt Formate. Das Snippet erkennt dies und verwendet statt dessen ein passendes der vorhandenen Formate.
  • Das Snippet lädt die genannten Bildformate in den zwei Dateiformaten JPG und WEBP und stellt beide dem Browser zur Auswahl zur Verfügung. Der Browser entscheidet selbst, welches Format geladen wird. Dies dient der Kompatibilität mit älteren Browsern.
  • Das Snippet stellt dem Browser alle Bildgrößen zur Auswahl. Der Browser entscheidet anhand der aktuellen Größe des Browserfensters (nicht der tatsächlichen Anzeigegröße des Bildes!), welche Bildgröße geladen wird.

Zwischenspeicher

Die von YouTube geladenen Vorschaubilder werden automatisch am Server im Verzeichnis /wp-content/uploads/ma-gdpr-youtube-thumbnails/ gespeichert. Das Verzeichnis wird automatisch angelegt, falls es noch nicht existiert. Für jede Video-ID wird darin ein eigenes Unterverzeichnis angelegt.

Shortcode Parameter

Neben der Pflicht-Angabe des id Parameters erlaubt der Shortcode noch einige weitere Parameter, die nachfolgend erklärt sind.

aspect-ratio

Das Standard-Seitenverhältnis für YouTube Videos ist 16:9.
Der Parameter aspect-ratio erlaubt die Verwendung eines davon abweichenden Seitenverhältnisses, wie z.b. 4:3 oder 1:1 (quadratisch).
Es können hier beliebige Seitenverhältnisse angegeben werden, solange die Syntax "Breite:Höhe" als zwei Zahlen durch Doppelpunkt getrennt eingehalten wird.
Das Vorschaubild im Format 16:9 wird automatisch in das hier angegebene Seitenverhältnis eingepasst.

Beispiel:

[ma-gdpr-youtube id="vPhg6sc1Mk4" width="400px" aspect-ratio="1:1"]

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

gdpr-text

Im Code Snippet ist ein Hinweis-Text zu den Datenschutzrichtlinien in verschiedenen Sprachen hinterlegt:

SpracheText
DANår du har trykket, vil videoen blive indlæst fra YouTube's servere. Se vores privatlivspolitik for flere informationer.
DEBei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
ENWhen clicked, this video is loaded from YouTube servers. See our privacy policy for details.
ESAl hacer clic, este vídeo se carga desde los servidores de YouTube. Consulte la política de privacidad para más detalles.
FREn cliquant, cette vidéo est chargée depuis les serveurs de YouTube. Voir la politique de confidentialité.
HUKattintás után ez a videó a Youtube szervereiről kerül lejátszásra. A részletekért olvassa el az Adatkezelési Tájékoztatót oldalt.
ITQuando si clicca, questo video viene caricato dai server di YouTube. Vedere l'informativa sulla privacy per i dettagli.

Die Sprache wird basierend auf der Sprache der Website oder Seite ausgewählt. Die Unterstützung von Polylang-Sprachen ist im Snippet enthalten.
Wenn kein Standardtext für die Sprache der Seite verfügbar ist, wird stattdessen Englisch verwendet.

Der Hinweis-Text kann per Parameter gdpr-text an die eigenen Anforderungen, beispielsweise andere Sprachen, angepasst werden, und kann folgende Platzhalter enthalten:
{privacy-policy-url} wird ersetzt durch den in WordPress konfigurierten URL der Seite zur Datenschutzerklärung.
{privacy-policy-link} wird ersetzt durch einen vollständigen Link zur in WordPress konfigurierten Seite zur Datenschutzerklärung.

Hinweis: Die vordefinierten Standardtexte enthalten einen Link zur Datenschutzerklärung, wenn diese in WordPress korrekt konfiguriert ist.

Beispiel:

[ma-gdpr-youtube id="vPhg6sc1Mk4" gdpr-text="Wanneer erop wordt geklikt, wordt deze video van de YouTube-servers geladen. Zie het {privacy-policy-link} voor details."]

Wanneer erop wordt geklikt, wordt deze video van de YouTube-servers geladen. Zie het Datenschutzerklärung voor details.

Hinweis: In diesem Fall wird der deutsche Titel der Datenschutzerklärung angezeigt, da dies die Standardsprache für diese Seite ist.

Der Parameter gdpr-text kann auch gezielt gelöscht werden. In diesem Fall wird dann kein Hinweis-Text angezeigt. Dies ist vor allem in Verbindung mit dem Parameter new-window denkbar.

[ma-gdpr-youtube id="vPhg6sc1Mk4" gdpr-text=""]

gdpr-text-size

Der Hinweistext wird standardmäßig in der Schriftgröße 0.7em, also 70% der für diesen Block definierten Textgröße, ausgegeben.
Mit dem Parameter gdpr-text-size an die eigenen Wünsche angepasst werden. Erlaubt sind alle gültigen CSS Angaben zur Text-Größe.

Beispiel:

[ma-gdpr-youtube id="vPhg6sc1Mk4" gdpr-text-size="20px"]
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

notice-style

Das Design des Datenschutzhinweises kann bei Bedarf angepasst werden.
Weiße Schrift auf rotem Balken? Kein Problem!

Beispiel:

[ma-gdpr-youtube id="vPhg6sc1Mk4" notice-style="background-color:red; color:white"]
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

notice-class

Mit einer eigenen CSS Klasse kann das noch gezielter erfolgen. So kann man z.B. auch das Design des Links steuern:

Beispiel:

<style>
body .my-notice-class {
  background-color: red;
  color:white;
}
body .my-notice-class a {
  color: white;
  font-weight: bold;
}
</style>
[ma-gdpr-youtube id="vPhg6sc1Mk4" notice-class="my-notice-class"]
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

width

Im Standard wird für den Video-Block eine Breite von 100% gesetzt. Der Video-Block nimmt dadurch die gesamte Breite des umschließenden Blocks, z.B. eines DIVs oder eine Spalte ein. Der Standard von 100% erlaubt eine einfache Anpassung der Responsive-Ansicht durch das übergeordnete Seitenelement.
Die Höhe errechnet sich automatisch aus der Breite und dem Seitenverhältnis.

Bei Bedarf kann die Breite mit dem Parameter width geändert werden. Erlaubt sind alle gültigen CSS Angaben zur Breite eines Elements.

Beispiel:

[ma-gdpr-youtube id="vPhg6sc1Mk4" width="300px"]

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

Hinweis: Ich empfehle, die Breite auf dem Standardwert von 100% zu belassen und stattdessen den umschließenden Block für Responsive Viewports mit CSS-Medienabfragen zu gestalten.

alt / title

Die Parameter alt und title setzen die entsprechenden HTML Attribute für das Vorschaubild. Das Attribut alt dient der Unterstützung von Suchmaschinen und der Barrierefreiheit, das Attribut title stellt den Tooltip Text bereit.

[ma-gdpr-youtube id="vPhg6sc1Mk4" alt="Video mit Meeresrauschen" title="Meeresrauschen"]

Video mit Meeresrauschen
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

thumbnail

Mit dem Parameter thumbnail kann ein eigenes statt des von YouTube geladenen Vorschaubildes verwendet werden.
Hier kann entweder ein vollständiger URL zu einem Bild angegeben werden, oder die Medien-ID eines Bildes, das bereits in WordPress hochgeladen wurde.

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

[ma-gdpr-youtube id="vPhg6sc1Mk4" thumbnail="https://www.altmann.de/wp-content/uploads/gdpr-youtube-beach.jpg"]

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

[ma-gdpr-youtube id="vPhg6sc1Mk4" thumbnail="1682"]

title-text

Das Snippet zeigt das Vorschau-Bild von YouTube, aber keinen Titel zu dem Video.
Mit dem Parameter title-text kann ein Titel über dem Vorschaubild angezeigt werden.

[ma-gdpr-youtube id="vPhg6sc1Mk4" title-text="Meeresrauschen"]
Meeresrauschen
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

Der Titel wird standardmäßig oben links angezeigt, weiße Schrift in Standardgröße, mit einem subtilen Schatten, damit der Text auch auf hellen Vorschaubildern lesbar ist.

title-style

Der Standard Style für den Titel ist definiert als:

.ma-gdpr-youtube-title {
  position:absolute; 
  width: 100%; 
  top: 1em; 
  padding: 0 1em; 
  color: white; 
  text-shadow: black 1px 1px 2px;
}

Über den Parameter title-style können Positionierung und Design des Titels angepasst werden.

Positionierung

Meeresrauschen
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
[ma-gdpr-youtube id="vPhg6sc1Mk4"title-text="Meeresrauschen" title-style="top:unset; bottom:30%;text-align:center"]

Design

Meeresrauschen
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
[ma-gdpr-youtube id="vPhg6sc1Mk4" title-text="Meeresrauschen" title-style="top:1rem; font-size:2em; color:red; text-align:center;"]

title-class

Zur globalen Steuerung von Positionierung und Design der Titel kann mit dem Parameter title-class auch eine Klasse festgelegt werden.

Beispiel:

<style>
body .my-video-title-class {
  top: 1rem; 
  font-size: 1.5em; 
  line-height: 1;
  color: blue; 
  text-align: center;
}
</style>
[ma-gdpr-youtube id="vPhg6sc1Mk4" title-text="Meeresrauschen mit Klasse" title-class="my-video-title-class"]
Meeresrauschen mit Klasse
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

play-button

Das Snippet stellt ab Version 1.2.0 verschiedene Play Button Typen zur Verfügung.
Standard ist der Button Type youtube. dieser braucht nicht explizit angegeben zu werden.
Über den Parameter play-button kann man den gewünschten Button auswählen:

Button Type youtube

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
[ma-gdpr-youtube id="vPhg6sc1Mk4" play-button="youtube"]

Button Typ circle

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
[ma-gdpr-youtube id="vPhg6sc1Mk4" play-button="circle"]

Button Typ circle-o

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
[ma-gdpr-youtube id="vPhg6sc1Mk4" play-button="circle-o"]

play-button-color

Die Farbe des YouTube Play Buttons ist #f61c0d;. Die beiden anderen Button Typen haben weiß als Standardfarbe.
Mit dem Parameter play-button-color kann eine abweichende Farbe für den Button festgelegt werden.

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
[ma-gdpr-youtube id="vPhg6sc1Mk4" play-button-color="blue"]
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
[ma-gdpr-youtube id="vPhg6sc1Mk4" play-button="circle" play-button-color="green"]
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.
[ma-gdpr-youtube id="vPhg6sc1Mk4" play-button="circle-o" play-button-color="black"]

play-button-class

Mit dem Parameter play-button-class kann eine eigene CSS Klasse für den Play Button verwendet werden.
Ein grüner Play Button auf der Seite zu "Grüner Energie" (Post ID 1205)? Kein Problem!

Beispiel:

<style>
body.postid-1205 .my-play-button {
  color:green;
}
</style>
[ma-gdpr-youtube id="vPhg6sc1Mk4" play-button-class="my-play-button"]
Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

new-window

Der Parameter new-window öffnet das Video in einem neuen Browser-Tab direkt auf der YouTube Seite.
Dies ist eventuell wünschenswert, wenn man in WordPress nur sehr kleine Vorschaubilder der Videos anzeigen will, die für eine direkte Einbindung des Players ungeeignet sind.

[ma-gdpr-youtube id="vPhg6sc1Mk4" new-window=1]

Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe Datenschutzerklärung.

Weitere YouTube Parameter

YouTube bietet selbst noch weitere Parameter für die Steuerung der Video-Wiedergabe und das Aussehen des Players an.
Eine Übersicht über die verfügbaren Parameter gibt es hier: https://developers.google.com/youtube/player_parameters
Achtung! Nur die englische Version dieser Seite ist auf einem aktuellen Stand. Die Sprachumschaltung erfolgt auf der Seite oben rechts mittels Sprach-Auswahlliste.

Beispiele:

ParameterBedeutung
rel=0Bei Pausierung bzw. am Ende des Videos werden "Ähnliche Videos" angezeigt. Das lässt sich nicht abschalten.
Man kann aber steuern, welche Videos angezeigt werden:
rel=1 Ähnliche Videos aus dem gesamten YouTube Archiv
rel=0 Ähnliche Videos nur aus dem aktuelle Kanal
modestbranding=1Standardmäßig wird am unteren Rand des Players ein YouTube Logo angezeigt.
Mit modestbranding=1 wird dieses Logo ausgeblendet.
fs=0Standardmäßig wird eine Symbolschaltfläche angezeigt zur Vollbild-Darstellung des Videos.
Mit fs=0 kann diese Schaltfläche ausgeblendet werden.

YouTube Player API

Bei Klick auf ein Video wird per YouTube Player API der YouTube Player von den YouTube Servern geladen.
Alle von YouTube gewohnten Funktionen sind hier verfügbar.
Den YouTube Player habe ich so implementiert, dass bei Klick auf ein Video ein eventuell anderes, bereits laufendes Video pausiert wird. So laufen nie zwei Videos gleichzeitig.

Download

Das Code Snippet steht hier zum Download zur Verfügung:

ma-gdpr-youtube.code-snippets.json
Version 1.2.0, 2022-09-25

Zur Beachtung: Der Titel des Code Snippets hat sich geändert von früher "GDPR YouTube" zu jetzt "MA GDPR YouTube".

Zur Installation und Nutzung dieser JSON Datei wird das Plugin Code Snippets oder Advanced Scripts benötigt.
Dort kann diese JSON Datei mit der Funktion "Import" hochgeladen und anschließend aktiviert werden.

Alternativ: Am Ende dieser Seite kann der vollständige Source Code des Snippets eingesehen und kopiert werden.

Im Change Log sind neue Funktionalitäten und Fehlerbehebungen dokumentiert.

Disclaimer

Das Code Snippet habe ich nach bestem Wissen und Gewissen getestet unter
- WordPress 6.0.2
- Oxygen 3.9, 4.0.4
- PHP 7.4, 8.0, 8.1
Ich stelle das Code Snippet zur freien Verwendung zur Verfügung.
Eine Garantie für die Funktionalität in allen denkbaren WordPress Umgebungen kann ich nicht geben. 
Download und Nutzung dieses Code Snippets erfolgen auf eigene Gefahr und Verantwortung.

Spenden

Es macht mir viel Freude, Code Snippets zu entwickeln und damit Anforderungen zu lösen. Die Snippets stelle ich kostenfrei zur Verfügung.

Wenn Du möchtest, kannst Du meine vielen Stunden Arbeit mit einer kleinen Kaffee-Spende über PayPal honorieren.

  Bei Klick auf den Button wird eine Verbindung zu PayPal aufgebaut.

Spenden werden selbstverständlich ordnungsgemäß durch mich versteuert.

Change Log

Siehe "Version History" in Source Code

Source Code

<?php
/*
Plugin Name:	MA GDPR YouTube
Description:	GDPR compliant YouTube video embedding
Author:			<a href="https://www.altmann.de/">Matthias Altmann</a>
Project:		Code Snippet: GDPR Compliant YouTube Embed
Version:		1.2.0
Plugin URI:		https://www.altmann.de/blog/code-snippet-gdpr-compliant-youtube-videos/
Description:	en: https://www.altmann.de/blog/code-snippet-gdpr-compliant-youtube-videos/
				de: https://www.altmann.de/blog/code-snippet-dsgvo-konforme-youtube-videos/
Copyright:		© 2021-2022, Matthias Altmann

Version History:
Date		Version		Description
--------------------------------------------------------------------------------------------------------------
2022-09-25	1.2.0		New Features:
						- Added new shortcode parameters title-text, title-class, title-style for title overlay
						- Added new shortcode parameters notice-class, notice-style for gdpr text banner
						- Added new shortcode parameters play-button, play-button-color, play-button-style for button variations
						Fixes:
						- Added original image size to source set for custom thumbnail by ID
						- Removed double '.' for German GDPR text.
						  (Thanks to Tobias Maximilian Hietsch for reporting)
						- Removed excess trailing comma at sprintf arguments
						  (Thanks to Nils Bäßler for reporting)
2022-02-07	1.1.0		New Features:
						- Support for webp thumbnail image format
						  (Requested by Artur Gilbert, Yan Kiara)
						- Added lazy loading for thumbnail images
						  (Requested by Yan Kiara)
						- Support for additional YouTube player parameters (e.g. modestbranding=1)
						  (Requested by Lau Fa)
						- Added Dansk translations for notice text and button label
						  (Thanks to Theis L. Soelberg)
						- Added shortcode parameters alt and title for thumbnail image
						  (Requested by Yan Kiara)
						- Added shortcode parameters thumbnail (URL or media ID)
						  (Requested by Viorel-Cosmin Miron)
						- Added width/height attributes for thumbnail images
						  (Requested by Viorel-Cosmin Miron)
						- Added JS console debugging by URL parameter "debug"
						Fixes:
						- Optimization of SVG symbol minimizing
2021-08-05	1.0.6		Features:
						- Using scheme-less URL to avoid issues with wrong WordPress URL configuration
						- Added parameter new-window to play video in a new window
						- Added "_" to valid character check on video id
						- Hide GDPR notice block if text is empty
						- Load and cache YouTube thumbnails only on very first appearance of a new video ID 
						  to improve performance, if specific YouTube thumbnail sizes are not available
						Bug Fixes:
						- Check for availability of specific thumbnail sizes (might not be available from YouTube)
2021-06-17	1.0.5		Fix: Correction in Hungarian translation
2021-06-17	1.0.4		Features: 
						- Added "-" to valid character check on video id (thanks to Zoltán Kőrösi)
						- Added Hungarian GDPR text (thanks to Zoltán Kőrösi)
2021-06-17	1.0.3		Fix: Check GET parameter "ct_builder" before accessing it
2021-06-15	1.0.2		Feature: Add link to privacy policy to default gdpr text if configured in WordPress
2021-06-15	1.0.1		Fix: Allow same video embedded multiple times
2021-06-15	1.0.0		Initial Release
--------------------------------------------------------------------------------------------------------------
*/


if (!class_exists('MA_GDPR_YouTube')) :

class MA_GDPR_YouTube {

	const TITLE							= 'MA GDPR YouTube';
	const SLUG							= 'ma-gdpr-youtube';
	const VERSION						= '1.2.0';

	// ===== CONFIGURATION ==============================================================================================
	public static $timing				= false; 	// Write timing info to wordpress debug.log if WP_DEBUG enabled		
	public static $debug				= false; 	// Write debug info to wordpress debug.log if WP_DEBUG enabled	

	private static $default_aspect_ratio		= '16:9';		// aspect ratio of the video block. Syntax X:X
	private static $default_gdpr_text 			= [ 			// GDPR notice text in different languages.  
		'da' => ['Når du har trykket, vil videoen blive indlæst fra YouTube\'s servere. Se vores %s for flere informationer.','privatlivspolitik'],
		'de' => ['Bei Klick wird dieses Video von den YouTube Servern geladen. Details siehe %s.', 'Datenschutzerklärung'],
		'en' => ['When clicked, this video is loaded from YouTube servers. See our %s for details.', 'privacy policy'],
		'es' => ['Al hacer clic, este vídeo se carga desde los servidores de YouTube. Consulte la %s para más detalles.', 'política de privacidad'],
		'fr' => ['En cliquant, cette vidéo est chargée depuis les serveurs de YouTube. Voir la %s.', 'politique de confidentialité'], 
		'hu' => ['Kattintás után ez a videó a Youtube szervereiről kerül lejátszásra. A részletekért olvassa el az %s oldalt.', 'Adatkezelési Tájékoztatót'],
		'it' => ['Quando si clicca, questo video viene caricato dai server di YouTube. Vedere %s per i dettagli.', 'l\'informativa sulla privacy'],
	]; 
	private static $default_gdpr_text_size		= '.7em';		// font size for GDPR text
	private static $default_width				= '100%';		// width of the video block. Can be specified in %, px
	private static $default_new_window			= false;		// open video in new window





	// ===== INTERNAL ===================================================================================================
	private static $debug_level_base	= 0;		// initial debug level - for indentation of debug messages
	private static $thumbnails_base		= null;		// will be set to the thumbnail base folder dir and url
	private static $footercode_needed	= false;	// will be set to true if shortcode used on current page
	private static $footercode_minimize = true;		// should we minimize all footer code (style, script, svg)?
	private static $yt_image_url 		= 'https://img.youtube.com/vi%s/%s/%s.%s';
	private static $yt_image_sizes		= [ // various resolutions, not all might be available!
	// see https://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
	//	tag		 						  aspect ratio	availability	resolution
	//	'default' 			=> 120,		// 		4:3		guaranteed		120x90
		'mqdefault' 		=> 320,		// 		16:9	guaranteed		320x180
		'hqdefault'			=> 480,		// 		4:3		guaranteed		480x360
		'sddefault'			=> 640,		// 		4:3		optional		640x480
		'hq720'				=> 1280,	// 		16:9	optional		1280x720
		'maxresdefault'		=> 1920,	// 		16:9	optional		(highest, depends on video, e.g. 1280x720, 1920x1080, ...)
	]; 

	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Initialize Snippet: 
	 * - Add shortcode "ma-gdpr-youtube"
	 * - Register hook action for wp_footer to emit footer code (style, script)
	 * - Set flag to emit footer code (style, script) when in Oxygen Builder
	 */
	public static function init() {
		$st = microtime(true);
		if (WP_DEBUG && (self::$timing || self::$debug)) {self::$debug_level_base = count(debug_backtrace()) -1;}

		self::$thumbnails_base = self::get_thumbnails_base();
		if (!self::$thumbnails_base) 	{self::set_admin_notice('warning', 'Error creating thumbnail cache base folder.'); return;}



		add_shortcode('ma-gdpr-youtube', [__CLASS__, 'shortcode']);
		add_action('wp_footer',[__CLASS__,'footercode']);

		if ( isset($_GET['ct_builder']) && ($_GET['ct_builder'] == true) ) {
			// emit styles, script, svg when Oxygen Builder is active
			self::$footercode_needed = true;
		}
		$et = microtime(true);
		if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s() Timing: %.5f sec.', self::get_callstack_padding(), __CLASS__, __FUNCTION__, $et-$st));}
	}
	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Return padding for debug/timing strings. Evaluated based on initial debug_backtrace count. 
	 * @return int			The callstack level
	 */
	private static function get_callstack_padding(){
		return str_pad('',count(debug_backtrace()) - self::$debug_level_base);
	}
	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Set a WP Admin notice
	 * @param string $type		The notice type (error, warning, success, info)
	 * @param string $msg		The message
	 */
	private static function set_admin_notice($type, $msg) {
		add_action('admin_notices', function(){
			echo sprintf('<div class="notice notice-%s"><p>[%s] %s</p></div>', $type, MA_GDPR_YouTube::TITLE, $msg);
		});
		error_log(sprintf('%s%s::%s() %s.', self::get_callstack_padding(), __CLASS__, __FUNCTION__, ucfirst($type).': '.$msg)); 

	}
	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Return base dir/url for video thumbnails. 
	 * Create directory /wp-content/ma-gdpr-youtube-thumbnails/ if necessary
	 * @return object		A dirinfo object (->dir, ->url)
	 */
	private static function get_thumbnails_base() {
		$st = microtime(true);

		$retval = (object)['dir'=>null,'url'=>''];
		$thumbnail_dir_info = wp_get_upload_dir();
		$retval->dir = $thumbnail_dir_info['basedir'].'/ma-gdpr-youtube-thumbnails';
		$retval->url = $thumbnail_dir_info['baseurl'].'/ma-gdpr-youtube-thumbnails';
		// create thumbnails folder if not exists
		if (!file_exists($retval->dir)) {
			if (!@mkdir($retval->dir)) {
				error_log(sprintf('[%s] Error creating thumbnail cache base folder.', self::TITLE)); 
				return null;
			}
		}
		// create scheme-less URL
		$retval->url = preg_replace('/^https?\:/','',$retval->url);

		$et = microtime(true);
		if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s() Timing: %.5f sec.', self::get_callstack_padding(), __CLASS__, __FUNCTION__, $et-$st));}

		return $retval;
	}
	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Return a link to the privacy policy page (if configured in WordPress) or just the passed text
	 * @param string $text	The text to return if privacy policy page is not defined in WordPress
	 * @return string		The HTML link element for the privacy policy page or the initial text
	 */
	private static function get_privacy_policy_link($text) {
		// 
		$pplink = get_the_privacy_policy_link();
		return  $pplink ? $pplink : $text; 
	}

	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Handle the shortcode "ma-gdpr-youtube". 
	 * @param array $atts		The shortcode attributes
	 * @param string $content	The content of the shortcode
	 * @return string			The output
	 */
	public static function shortcode($atts, $content = '') {
		$st = microtime(true);
		$lang = self::get_current_language();

		// get defaults for unspecified atts
		$atts_default = [
			'id'				=> null,
			'uniqid'			=> null,
			'width'				=> self::$default_width,
			'aspect-ratio'		=> self::$default_aspect_ratio,
			'notice-class'		=> null,
			'notice-style'		=> null,
			'gdpr-text'			=> isset(self::$default_gdpr_text[$lang]) 
									? sprintf(self::$default_gdpr_text[$lang][0],self::get_privacy_policy_link(self::$default_gdpr_text[$lang][1])) 
									: sprintf(self::$default_gdpr_text['en'][0],self::get_privacy_policy_link(self::$default_gdpr_text['en'][1])),
			'gdpr-text-size'	=> self::$default_gdpr_text_size,
			'alt'				=> '',
			'title'				=> '',
			'thumbnail'			=> null,
			'title-text'		=> null,
			'title-class'		=> null,
			'title-style'		=> null,
			'play-button'		=> 'youtube', // currently supported: youtube, circle, circle-o
			'play-button-style'	=> null,
			'play-button-color'	=> null,
			'new-window'		=> self::$default_new_window,
		];
		$atts = (object)array_merge($atts_default, $atts);

		// any other parameter will be passed to youtube directly
		// see https://developers.google.com/youtube/player_parameters?hl=de#Parameters for a list of parameters
		// Note! rel=0 not supported anymore since September 2019 - without hacks which we won't support
		// See https://www.amblemedia.com/disable-suggested-videos-on-youtube-embeds/
		$yt_parameters = [];
		foreach ($atts as $att_key => $att_val) {
			if (!in_array($att_key,array_keys($atts_default))) {
				$yt_parameters[$att_key] = $att_val;
			} 
		}
		$yt_parameters_json = json_encode($yt_parameters);


		if (!isset($atts->id) || ($atts->id == '' )) 			{return sprintf('[%s] Missing video id.',self::TITLE);}
		if (preg_match('/[^A-Za-z0-9\-\_]/',$atts->id))			{return sprintf('[%s] Invalid video id.',self::TITLE);}
		// generate an unique id (for the case a video is embedded multiple times)
		$atts->uniqid = $atts->id.'-'.uniqid();

		if (!self::$thumbnails_base) 							{return sprintf('[%s] Error creating thumbnail directory.',self::TITLE);}
		// check if we already have a thumbnail

		if (!self::check_thumbnails($atts->id)) 				{return sprintf('[%s] Error retrieving thumbnails.',self::TITLE);}

		$thumbnail = '';
		$sources = [];

		if ($atts->thumbnail) {
			if (is_numeric($atts->thumbnail)) {
				// numeric value => media id
			
				// get available image sizes
				$metadata = wp_get_attachment_metadata($atts->thumbnail);

				$image_sizes = [];
				// add original size
				$img_src = wp_get_attachment_image_src($atts->thumbnail,'full');	// '0': url, '1': width, '2': height, '3': resized
				$mime_type = get_post_mime_type($atts->thumbnail);
				$image_sizes['original'] = [
					'file'		=> $metadata['file'],
					'width'		=> $metadata['width'],
					'height'	=> $metadata['height'],
					'mime-type'	=> $mime_type,
					'filesize'	=> $metadata['filesize']??null,
					'url'		=> $img_src[0],
					'key'		=> 'full',
				];
				// add resized sizes
				foreach ($metadata['sizes'] as $key => $data) {
					// retrieve url for specific size
					$img_src = wp_get_attachment_image_src($atts->thumbnail,$key);	// '0': url, '1': width, '2': height, '3': resized
					$data['url'] = $img_src['0']; 
					$data['key'] = $key;
					$image_sizes[$key] = $data;
				}
				// sort image sizes by width ascending
				uasort($image_sizes, function($a,$b){
					if ($a['width'] == $b['width']) {return 0;}
					return ($a['width'] < $b['width']) ? -1 : 1;
				});

				// get largest image 
				$largest = (object)end($image_sizes);

				if (true) { // variant 1 by <picture> <source ...> <source ...> <img ...> </picture>
					foreach ($image_sizes as $key => $data) {
						// to improve thumbnail quality, use higher res image if we reach half its size 
						$sources[] = '<source media="(min-width:'.($data['width']/2).'px)" type="'.$data['mime-type'].'" srcset="'.$data['url'].'">';
					}
					// create thumbnail
					$thumbnail .= sprintf('<picture class="ma-gdpr-youtube-thumbnail">%s <img loading="lazy" src="%s" width="%s" height="%s" alt="%s" title="%s"></picture>',
										implode('',array_reverse($sources)), $largest->url, $largest->width, $largest->height, $atts->alt, $atts->title);

				}

				if (false) { // variant 2 by <picture> <img ... srcset ... sizes ... > </picture>
					
					$srcset = wp_get_attachment_image_srcset($atts->thumbnail, $largest->key);
					// create sizes attribute 
					$sizes = [];
					foreach ($image_sizes as $key => $data) {
						$sizes[] = '(min-width: '.($data['width']/2).'px) '.$data['width'].'px';
					}
					// largest as default in last position
					$sizes[] = $largest->width.'px';
					$sizes = implode(', ',$sizes);
					// create thumbnail
					$thumbnail .= '<picture class="ma-gdpr-youtube-thumbnail">';
					$thumbnail .= sprintf('<img loading="lazy" width="%s" height="%s" src="%s" alt="%s" title="%s" srcset="%s" sizes="%s">',
									$largest->width, $largest->height, $largest->url, $atts->alt, $atts->title, /*implode(', ',$srcsets) */ $srcset, $sizes);
					$thumbnail .= '</picture>';
				}

	
			} else {
				$sources = [];
				$thumbnail .= '<picture class="ma-gdpr-youtube-thumbnail">';
				$thumbnail .= sprintf('<img loading="lazy" src="%s" alt="%s" title="%s">',$atts->thumbnail, $atts->alt, $atts->title);
				$thumbnail .= '</picture>';
			}
		} else {
			$source_list = [];
			$sizes = [];
			// get sources
			foreach (self::$yt_image_sizes as $size_tag => $size) {
				$source_list[] = [$size_tag,$size];
			}
			// get smallest thumbnail first
			list ($size_tag,$size) = array_shift($source_list);
			$img_src = self::$thumbnails_base->url.'/'.$atts->id.'/'.$atts->id.'_'.$size_tag.'.jpg';
			// get larger thumbnails
			while (count($source_list)) {
				list ($size_tag,$size) = array_shift($source_list);
				foreach(['jpg'=>'jpeg','webp'=>'webp',] as $ext => $mime) { // will be reversed! so webp before jpg to have jpg before webp in final output
					$img_path = self::$thumbnails_base->dir.'/'.$atts->id.'/'.$atts->id.'_'.$size_tag.'.'.$ext;
					if (file_exists($img_path)) {
						// get real image size
						$img_info = getimagesize($img_path);
						if ($img_info) {
							$img_width = $img_info[0];
							// skip if we already have this size/format combo (maxres might be same as hq720)
							if (in_array($img_width.'_'.$ext,$sizes)) {continue;}
							$sizes[] = $img_width.'_'.$ext;
							// to improve thumbnail quality, use higher res image if we reach half its size 
							$sources[] = '<source media="(min-width:'.($img_width/2).'px)" type="image/'.$mime.'" srcset="'.self::$thumbnails_base->url.'/'.$atts->id.'/'.$atts->id.'_'.$size_tag.'.'.$ext.'">';
						}
					}
				}
			}
			$thumbnail .= '<picture class="ma-gdpr-youtube-thumbnail">' . implode('',array_reverse($sources)) . '<img src="'.$img_src.'" alt="'.$atts->alt.'" title="'.$atts->title.'"></picture>';
		}

		// calculate dimensions of video block depending on width and aspect ratio
		list ($arw,$arh) = explode(':',$atts->{'aspect-ratio'},2); // aspect ratio elements
		list ($width_value, $width_unit) = ['100','%']; // default width value and unit
		// split width value and unit
		preg_match('/^(\d+)(.+)$/',$atts->width,$matches);
		if (count($matches) == 3) {array_shift($matches); list ($width_value, $width_unit) =  $matches;}
		// calculate block dimensions
		$block_width = $width_value.$width_unit;
		$block_height = ($width_value * ($arh/$arw)) . $width_unit;

		// privacy policy url and link
		$atts->{'gdpr-text'} = str_replace('{privacy-policy-url}', get_privacy_policy_url(), $atts->{'gdpr-text'});
		$atts->{'gdpr-text'} = str_replace('{privacy-policy-link}', get_the_privacy_policy_link(), $atts->{'gdpr-text'});

		// title overlay
		$title_overlay = !empty($atts->{'title-text'})
			? sprintf('<div class="ma-gdpr-youtube-title %1$s" %2$s>%3$s</div>',
				$atts->{'title-class'} ?? '',
				$atts->{'title-style'} ? 'style="'.$atts->{'title-style'}.'"' : '',
				$atts->{'title-text'}
				)
			: '';

		// play button style, color
		$play_button_style = '';
		if ($atts->{'play-button-style'}) {$play_button_style .= $atts->{'play-button-style'}.';';}
		if ($atts->{'play-button-color'}) {$play_button_style .= 'color:'.$atts->{'play-button-color'}.';';}
		if ($play_button_style) {$play_button_style = 'style="'.$play_button_style.'"';}


		$retval = sprintf(	'<div id="%7$s" data-video-id="%2$s" class="ma-gdpr-youtube-wrapper" style="width:%3$s;height:%4$s;padding-top:%4$s;" data-new-window="%8$s" data-yt-parameters="%9$s">'.
								$thumbnail.
								'<svg class="ma-gdpr-youtube-button button-%13$s %14$s" %15$s><use xlink:href="#ma-gdpr-youtube-play-button-%13$s"></use></svg>'.
								'%10$s'.
								'<div class="ma-gdpr-youtube-notice %11$s" style="font-size:%6$s; %12$s">%5$s</div>'.
							'</div>',
					/*1*/	self::$thumbnails_base->url, 
					/*2*/	$atts->id,
					/*3*/	$block_width,
					/*4*/	$block_height,
					/*5*/	$atts->{'gdpr-text'},
					/*6*/	$atts->{'gdpr-text-size'},
					/*7*/	$atts->uniqid,
					/*8*/	$atts->{'new-window'},
					/*9*/	count($yt_parameters) ? base64_encode($yt_parameters_json) : '',
					/*10*/	$title_overlay,
					/*11*/	$atts->{'notice-class'} ?? '',
					/*12*/	$atts->{'notice-style'} ?? '',
					/*13*/	$atts->{'play-button'} ?? '',
					/*14*/	$atts->{'play-button-class'} ?? '',
					/*15*/	$play_button_style
				);
		self::$footercode_needed = true;

		$et = microtime(true);
		if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s() Timing: %.5f sec.', self::get_callstack_padding(), __CLASS__, __FUNCTION__, $et-$st));}

		return $retval;

	}


	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Check if thumbnails for video $id have been downloaded. Load if not yet available.
	 * @param string $id	The YouTube video ID
	 * @return	bool		True if downloaded, false if not downloaded
	 */
	// 
	private static function check_thumbnails($id) {
		$st = microtime(true);

		$retval = false;

		if (!self::$thumbnails_base) 	{goto DONE;}
		// check if directory for this video exists
		$vid_dir = self::$thumbnails_base->dir.'/'.$id;
		if (!file_exists($vid_dir)) {
			if (!@mkdir($vid_dir)) {error_log(sprintf('[%s] Error creating thumbnail cache folder for video %s.', self::TITLE, $id)); goto DONE;}
			foreach (self::$yt_image_sizes as $size_tag => $size) {
				foreach(['jpg'=>'','webp'=>'_webp'] as $ext => $url_appendix) {

					$img_path = $vid_dir.'/'.$id.'_'.$size_tag.'.'.$ext;
					if (!file_exists($img_path)) {
						$img_url = sprintf(self::$yt_image_url, $url_appendix, $id, $size_tag, $ext);
						// load and cache thumbnail
						if ($img_data = @file_get_contents($img_url)) {
							file_put_contents($img_path, $img_data);
						}
					}
				}
			}
		}
		$retval = true;

		DONE:
		$et = microtime(true);
		if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s(%s) Timing: %.5f sec.', self::get_callstack_padding(), __CLASS__, __FUNCTION__, $id, $et-$st));}

		return $retval;
	}
	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Get the current page/post get_current_language
	 * @return string		The language code as e.g. "de", "en"
	 */
	private static function get_current_language(){
		$retval = get_locale();
		// Is Polylang available? 
		if (function_exists('pll_current_language')) {$retval = pll_current_language();}
		$retval = str_replace('_','-',$retval);
		$retval = explode('-',@$retval)[0];
		return $retval;
	}
	//-------------------------------------------------------------------------------------------------------------------
	/**
	 * Emits the footer code (styles, script) to handle the YouTube embedding
	 */
	public static function footercode() {
		$st = microtime(true);

		if (!self::$footercode_needed) {goto DONE;}
		// emit style
		$style = <<<'END_OF_STYLE'
		<style id="ma-gdpr-youtube-style">
			.ma-gdpr-youtube-wrapper {position:relative; display:flex;}
			.ma-gdpr-youtube-thumbnail {position:absolute; top:0; left:0; width:100%; height:100%; display:flex; cursor:pointer;}
			.ma-gdpr-youtube-thumbnail img {width:100%; height:100%; object-fit:cover; object-position:50% 50%;}
			.ma-gdpr-youtube-button {position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:70px; height:70px; cursor:pointer;}
			.ma-gdpr-youtube-button.button-youtube {color:#f61c0d;}
			.ma-gdpr-youtube-button.button-circle {color:white; filter:drop-shadow(0px 0px 4px darkgray);}
			.ma-gdpr-youtube-button.button-circle-o {color:white; filter:drop-shadow(0px 0px 4px darkgray);}
			.ma-gdpr-youtube-notice {position:absolute; width:100%; left:0; right:0; bottom:0; max-width:100%; text-align:center; font-size:.7em; background-color:rgba(255,255,255,.8); padding:.2em .5em;}
			.ma-gdpr-youtube-notice:empty {display:none;}
			.ma-gdpr-youtube-title {position:absolute; width:100%; top:1em; padding:0 1em; color:white; text-shadow: black 1px 1px 2px;}
		</style>
END_OF_STYLE;
		if (self::$footercode_minimize) { 
			$style = preg_replace('/\/\*.*?\*\//','',$style); 
			$style = preg_replace('/\r?\n */','',$style); 
			$style = preg_replace('/\t/','',$style); 

		}
		echo $style;
		// emit code
		// TODO: Migrate ES5 to ES6 (var)
		$script = <<<'END_OF_SCRIPT'
		<script id="ma-gdpr-youtube-script">
		var $magdpryt_debug = /[?&]debug/.test(location.search);

		function get_yt_parameters_from_wrapper($ytWrapper) {
			var $retval = null;
			var $yt_parameters = $ytWrapper.attr('data-yt-parameters');
			if ($yt_parameters) {
				$magdpryt_debug && console.log('yt-parameters RAW',$yt_parameters);
				$yt_parameters = atob($yt_parameters);
				$magdpryt_debug && console.log('yt-parameters JSON',$yt_parameters);
				$yt_parameters = JSON.parse($yt_parameters);
				$magdpryt_debug && console.log('yt-parameters Object',$yt_parameters);
				if ($yt_parameters.hasOwnProperty) {
					for (var $key in $yt_parameters) {
						if (!isNaN($yt_parameters[$key])) {$yt_parameters[$key] = parseInt($yt_parameters[$key]);}
					}
					$yt_parameters.enablejsapi = 1;
					$retval = $yt_parameters;
				}
			}
			return $retval;
		}

		jQuery(document).ready(function($){
				window.ma_gdpr_youtube_player = null;
				window.YT = null; /* prevent JS errors in Oxygen Builder */

				$('.ma-gdpr-youtube-wrapper :is(img,.ma-gdpr-youtube-button)').click(function() {
					/* get closest wrapper */
					var $ytWrapper = $(this).closest('.ma-gdpr-youtube-wrapper'); 
					/* check if video should be played in new window */
					var $new_window = $ytWrapper.attr('data-new-window')=='1';
					$magdpryt_debug && console.log('YouTube open in new-window:',$new_window);

					if ($new_window) {
						/* Get the video id from the parent div's id attribute */
						var $ytVidID = $ytWrapper.attr('data-video-id');
						/* check if additional yt parameters have been specified */
						var $yt_parameters = get_yt_parameters_from_wrapper($ytWrapper);
						var $ytp = $yt_parameters ? '&' + (new URLSearchParams($yt_parameters).toString()) : '';
						window.open('https://www.youtube.com/watch?v='+$ytVidID+$ytp,'video-'+$ytVidID);
						return;
					}

					/* Instantiate a new YT player (replacing out wrapper), and play it when ready */
					if ( $('#ma-gdpr-youtube-player-api').length==0 ) { /* check if youtube player api has already been loaded */
						/* Load the YouTube API */
						window.onYouTubeIframeAPIReady = function() {
							$magdpryt_debug && console.log('YouTube API ready.');
							window.ytVidPlay($ytWrapper);
						};
						$magdpryt_debug && console.log('Loading YouTube API...');
						$('<script id="ma-gdpr-youtube-player-api" src="https://www.youtube.com/iframe_api"><\/script>').appendTo('body');
					} else {
						/* YouTube API is already loaded */
						window.ytVidPlay($ytWrapper);
					}
				});
				
			
				/* This gets called when the onReady event fires */
				window.ytVidPlay = function($ytWrapper) {
					/* Get the video id from the parent div's id attribute */
					var $ytVidID = $ytWrapper.attr('data-video-id');
					var $apiID = $ytWrapper.attr('id');
					$magdpryt_debug && console.log('Starting video '+$ytVidID+' from wrapper '+$apiID);
					window.ma_gdpr_youtube_player && (typeof window.ma_gdpr_youtube_player.pauseVideo!=='undefined') && window.ma_gdpr_youtube_player.pauseVideo();
					/* Get the dimensions of the wrapper */
					var $ytWrapperWidth = $ytWrapper.innerWidth();
					var $ytWrapperHeight = $ytWrapper.innerHeight();
					$magdpryt_debug && console.log('Video WxH',[$ytWrapperWidth,$ytWrapperHeight]);

					/* remove styles from wrapper */
					$ytWrapper.css('height',$ytWrapperHeight+'px').css('padding','unset');

					if (!YT) return; /* prevent JS errors in Oxygen Builder */

					

					var $apiConfig = {
						width: $ytWrapperWidth,
						height: $ytWrapperHeight ,
						videoId: $ytVidID,
						host: 'https://www.youtube-nocookie.com',
						enablejsapi: 1,
						playerapiid: $apiID,
						rel: 0,
						events: {
							'onReady': function(event) { 
								$magdpryt_debug && console.log('Video ready.');
								window.ma_gdpr_youtube_player && (typeof window.ma_gdpr_youtube_player.playVideo!=='undefined') && window.ma_gdpr_youtube_player.playVideo();
							},
							'onStateChange': function(event) {
								/* if multiple YT players are open, stop others when one is (re-)started. */
								/* see https://stackoverflow.com/questions/15164942/stop-embedded-youtube-iframe */
								$magdpryt_debug && console.log('Event',event);
								if (event.data == 1) { /* play */
									$('iframe.ma-gdpr-youtube-wrapper').each(function() { /* check for wrappers that are already a YT iframe */
										if ( ($(this).attr('id') != $apiID) ) { /* skip the current video */
											$magdpryt_debug && console.log('Pausing other video '+$(this).attr('data-video-id')+'.');
											/* send pause command */
											$(this)[0].contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*')
										}
									});
								}
								if (event.data == 2) { /* pause */
									$magdpryt_debug && console.log('Pausing video '+event.target.playerInfo.videoData.video_id+'.');
								}
							}
						}
					};

					/* check if additional yt parameters have been specified */
					var $yt_parameters = get_yt_parameters_from_wrapper($ytWrapper);
					if ($yt_parameters) {
						$apiConfig.playerVars = $yt_parameters;
					}

					$magdpryt_debug && console.log('apiConfig',$apiConfig);
					window.ma_gdpr_youtube_player  = new YT.Player($apiID, $apiConfig);
				};
		});
		</script>
END_OF_SCRIPT;
		if (self::$footercode_minimize) { 
			$script = preg_replace('/\/\*.*?\*\//','',$script); 
			$script = preg_replace('/\r?\n */','',$script); 
			$script = preg_replace('/\t/','',$script); 
		}
		echo $script;



		// emit play button svg symbol
		$symbol = <<<'END_OF_SYMBOL'
			<svg id="ma-gdpr-youtube-symbols" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 
						aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;">
				<defs>
					<symbol id="ma-gdpr-youtube-play-button-youtube" viewBox="0 0 500 350" >
						<path fill="currentColor" d="M500,74.767C500,33.472,466.55,0,425.277,0 H74.722C33.45,0,0,33.472,0,74.767v200.467C0,316.527,33.45,350,74.722,350h350.555C466.55,350,500,316.527,500,275.233V74.767z  M200,259.578v-188.3l142.789,94.15L200,259.578z"/>
						<path fill="white" d="M199.928,71.057l0.074,188.537l142.98-94.182 L199.928,71.057z"/>
					</symbol>
					<symbol id="ma-gdpr-youtube-play-button-circle" viewBox="0 0 24 28" >
						<path fill="currentColor" d="M12 2c6.625 0 12 5.375 12 12s-5.375 12-12 12-12-5.375-12-12 5.375-12 12-12zM18 14.859c0.313-0.172 0.5-0.5 0.5-0.859s-0.187-0.688-0.5-0.859l-8.5-5c-0.297-0.187-0.688-0.187-1-0.016-0.313 0.187-0.5 0.516-0.5 0.875v10c0 0.359 0.187 0.688 0.5 0.875 0.156 0.078 0.328 0.125 0.5 0.125s0.344-0.047 0.5-0.141z"/>
					</symbol>
					<symbol id="ma-gdpr-youtube-play-button-circle-o" viewBox="0 0 24 28" >
						<path fill="currentColor" d="M18.5 14c0 0.359-0.187 0.688-0.5 0.859l-8.5 5c-0.156 0.094-0.328 0.141-0.5 0.141s-0.344-0.047-0.5-0.125c-0.313-0.187-0.5-0.516-0.5-0.875v-10c0-0.359 0.187-0.688 0.5-0.875 0.313-0.172 0.703-0.172 1 0.016l8.5 5c0.313 0.172 0.5 0.5 0.5 0.859zM20.5 14c0-4.688-3.813-8.5-8.5-8.5s-8.5 3.813-8.5 8.5 3.813 8.5 8.5 8.5 8.5-3.813 8.5-8.5zM24 14c0 6.625-5.375 12-12 12s-12-5.375-12-12 5.375-12 12-12 12 5.375 12 12z"/>
					</symbol>
				</defs>
			</svg>
END_OF_SYMBOL;
		if (self::$footercode_minimize) { $symbol = preg_replace('/\r?\n[\t ]*/','',$symbol); }
		echo $symbol;

		DONE:

		$et = microtime(true);
		if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s() Timing: %.5f sec.', self::get_callstack_padding(), __CLASS__, __FUNCTION__, $et-$st));}


	}

}

//===================================================================================================================
// Initialize

// Warn about incompatibilities (currently none)
add_action('wp_loaded',function(){
	if (is_admin()) {
		if (!isset($GLOBALS['MA_GDPR_YouTube_Incompatibilities'])) {$GLOBALS['MA_GDPR_YouTube_Incompatibilities'] = [];}
		if (count($GLOBALS['MA_GDPR_YouTube_Incompatibilities'])) {
			if (WP_DEBUG && (MA_GDPR_YouTube::$debug || MA_GDPR_YouTube::$timing)) 
				{error_log('MA_GDPR_YouTube / Incompatibilities: '.print_r($GLOBALS['MA_GDPR_YouTube_Incompatibilities'],true));}
			add_action('admin_notices', function(){
				if (WP_DEBUG ) {error_log('MA_GDPR_YouTube/ Incompatibilities: '.print_r($GLOBALS['MA_GDPR_YouTube_Incompatibilities'],true));}
				$implementation = basename(__FILE__) == 'ma-gdpr-youtube.php' ? 'Plugin' : 'Code Snippet';
				echo '<div class="notice notice-warning is-dismissible">
						<p>The '.$implementation.'  "'.MA_GDPR_YouTube::TITLE.'" is skipped: '.implode(' or ',$GLOBALS['MA_GDPR_YouTube_Incompatibilities']).'</p>
					</div>';
			});
		}
	}
}, 1000); 

add_action('wp_loaded',function(){
	if (isset($GLOBALS['MA_GDPR_YouTubeIncompatibilities']) && count($GLOBALS['MA_GDPR_YouTube_Incompatibilities'])) return;
	if (wp_doing_ajax()) 		return; 	// don't run for AJAX requests
	if (wp_doing_cron()) 		return; 	// don't run for CRON requests
	if (wp_is_json_request()) 	return; 	// don't run for JSON requests
	if (is_favicon()) 			return; 	// don't run for favicon request
	if (isset($_SERVER['QUERY_STRING']) && ($_SERVER['QUERY_STRING'] == 'service-worker'))			return;	// don't run for service-worker
	if (isset($_SERVER['REQUEST_URI']) 	&& ($_SERVER['REQUEST_URI'] == '/favicon.ico'))				return;	// don't run for favicon
	if (isset($_SERVER['REQUEST_URI']) 	&& (strpos($_SERVER['REQUEST_URI'],'/wp-content/') === 0))	return;	// don't run for dynamic wp-content file
	
	if (is_admin()) {
		global $pagenow;
		if (	($pagenow != 'post-new.php') 
		&& 	(	($pagenow != 'post.php') || ($pagenow == 'post.php' && isset($_REQUEST['action']) && ($_REQUEST['action'] != 'edit'))	) 
		) return; // only load on specific requests where Gutenberg is involved
	}
		
	$implementation = basename(__FILE__) == 'ma-gdpr-youtube.php' ? 'Plugin' : 'Code Snippet';
	if (WP_DEBUG && (MA_GDPR_YouTube::$debug || MA_GDPR_YouTube::$timing)) 
		{error_log(sprintf('%s Initializing %s for request URI="%s" action="%s"', MA_GDPR_YouTube::TITLE, $implementation, @$_SERVER['REQUEST_URI'], @$_REQUEST['action']));}

	MA_GDPR_YouTube::init();

}, 1200); 
	
endif;



Erstveröffentlichung: 15.06.2021 auf Code Snippet: DSGVO-konforme YouTube Videos
magnifier