Home / Admin / MA GDPR YouTube
Duplicate Snippet

Embed Snippet on Your Site

MA GDPR YouTube

[ma-gdpr-youtube id="youtubeid"]
GDPR compliant YouTube video embedding Version 1.2.0, 2022-09-25 © 2021-2022, Matthias Altmann Info: en: https://www.altmann.de/blog/code-snippet-gdpr-compliant-youtube-videos/ de: https://www.altmann.de/blog/code-snippet-dsgvo-konforme-youtube-videos/

Code Preview
php
<?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;

Comments

Add a Comment