{
  "generator": "Code Snippets v3.6.8",
  "date_created": "2025-07-11 20:52",
  "snippets": [
    {
      "id": 78,
      "name": "MA GDPR Vimeo",
      "desc": "<p>GDPR compliant Vimeo video embedding</p>\n<p>Version 1.4.2, 2025-07-11 <br />© 2023-2025, Matthias Altmann</p>\n<p>Info: <br />en: <a href=\"https://www.altmann.de/blog/code-snippet-gdpr-compliant-vimeo-videos/\" target=\"_blank\" rel=\"noopener\">https://www.altmann.de/blog/code-snippet-gdpr-compliant-vimeo-videos/</a> <br />de: <a href=\"https://www.altmann.de/blog/code-snippet-dsgvo-konforme-vimeo-videos/\" target=\"_blank\" rel=\"noopener\">https://www.altmann.de/blog/code-snippet-dsgvo-konforme-vimeo-videos/</a></p>",
      "code": "/*\nPlugin Name:\tMA GDPR Vimeo\nDescription:\tGDPR compliant Vimeo video embedding\nAuthor:\t\t\t<a href=\"https://www.altmann.de/\">Matthias Altmann</a>\nProject:\t\tCode Snippet: GDPR Compliant Vimeo Embed\nVersion:\t\t1.4.2\nPlugin URI:\t\thttps://www.altmann.de/blog/code-snippet-gdpr-compliant-vimeo-videos/\nDescription:\ten: https://www.altmann.de/blog/code-snippet-gdpr-compliant-vimeo-videos/\n\t\t\t\tde: https://www.altmann.de/blog/code-snippet-dsgvo-konforme-vimeo-videos/\nCopyright:\t\t© 2023-2025, Matthias Altmann\n\n\nNotes:\nOxygen Builder\n\tModals, on close, reset src attribute of iframes to stop running videos. \n\tThat is done by this.src = this.src, which also kills event handlers we assigned.\n\tIf a modal is closed with a playing video, it's stopped, but we'll lose control \n\tover it since we don't get any further events. \n\nTESTED WITH:\nProduct\t\tVersions\n--------------------------------------------------------------------------------------------------------------\nPHP \t\t8.1, 8.2, 8.3\nWordPress\t6.4.2 ... 6.8.1\nBricks\t\t1.9.5 ... 1.12.4\nOxygen\t\t4.8.1 ... 4.9.1\n--------------------------------------------------------------------------------------------------------------\n\nVersion History:\nDate\t\tVersion\t\tDescription\n--------------------------------------------------------------------------------------------------------------\n2025-07-11\t1.4.2\t\tFixes:\n\t\t\t\t\t\t- Fixed warning about undefined array key in parse_video_url()\n2024-10-11\t1.4.1\t\tFixes:\n\t\t\t\t\t\t- Fixed missing styling of video preview (play button, GDPR text) in Bricks builder.\n2024-07-04\t1.4.0\t\tNew Features:\n\t\t\t\t\t\t- Support for unlisted Vimeo videos with hash parameter \"h\" in shortcode or URL\n\t\t\t\t\t\t- Loading more thumbnail sizes from Vimeo \n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- Replaced deprecated Vimeo SimpleAPI by oEmebd\n\t\t\t\t\t\t- Cache JSON format has changed due to the change from SimpleAPI to oEmbed\n-\t\t\t1.3.1\t\tChanges\n\t\t\t\t\t\t- Added width and height attributes for thumbnail inages\n2024-04-28\t1.3.0\t\tReorganization of code base.\n\t\t\t\t\t\tNew Features:\n\t\t\t\t\t\t- For better accessibility, the Enter key can now be used to start and stop videos.\n\t\t\t\t\t\t  (Thanks to Stephan Koenigk for his feature request and üre-release tests)\n\t\t\t\t\t\tChanges:\n\t\t\t\t\t\t- For invalid IDs, don't create directory, store json, attempt to retrieve thumbnails\n\t\t\t\t\t\t- In Builders Bricks and Oxygen, click handler is deactivated to allow selecting element\n\t\t\t\t\t\t- Preparation for Bricks Element. Coming soon.\n2023-12-30\t1.2.0\t\tNew Features:\n\t\t\t\t\t\t- Complete rebuild of JS\n\t\t\t\t\t\t- Now using Vimeo Player API to provide more control\n\t\t\t\t\t\t- Video player management via players registry and observer\n\t\t\t\t\t\t\t- Pause current video if another one is started\n\t\t\t\t\t\t\t- Pause video if modal/popup closed (evaluated by visibility of parent DOM element)\n\t\t\t\t\t\t- Support for dynamically embedded videos using AJAX calls:\n\t\t\t\t\t\t\t- Removed init prevention for AJAX calls\n\t\t\t\t\t\t\t- Added PHP method MA_GDPR_Vimeo::enable_footercode() to trigger output of footer\n\t\t\t\t\t\t\t  code (styles, scripts, svg) for video embeds dynamically loaded by AJAX calls\n \t\t\t\t\t\t\t- Play click handler is now assigned as onclick event instead of collecting all \n\t\t\t\t\t\t\t  videos after page load. This eliminates the need for an extra click handler \n\t\t\t\t\t\t\t  initialization for players dynamically loaded after page load.  \n\t\t\t\t\t\t- Optimizations for accessibility \n\t\t\t\t\t\tFixes:\n\t\t\t\t\t\t- Prefixed wrapper/player IDs with snippet slug to prevent IDs starting with number\n\t\t\t\t\t\t- Fallback for aspect ratio via padding-top (CSS variable, @supports rule) \n\t\t\t\t\t\t  for older browsers not supporting aspect-ratio like Safari < V15\n2023-11-09\t1.1.1\t\tFix: \"Attempt to assign property 'notes' on null\" error in get_video_details()\n2023-04-04\t1.1.0\t\tNew Feature: Pause handler for closed Oxygen Modals\n2023-04-03\t1.0.0\t\tInitial Release\n2023-04-02\t0.0.0\t\tDevelopment start\n\nSamples\nhttps://vimeo.com/347119375\t> Sample Video\nhttps://vimeo.com/814361316 > Orbiting Earth\n\nDocumentation:\nhttps://developer.vimeo.com/api/oembed/videos\n\n\n*/\n\n\nif (!class_exists('MA_GDPR_Vimeo')) :\n\nclass MA_GDPR_Vimeo {\n\n\tpublic const TITLE\t\t= 'MA GDPR Vimeo';\n\tpublic const SLUG\t\t= 'ma-gdpr-vimeo';\n\tpublic const VERSION\t= '1.4.2';\n\n\t// ===== CONFIGURATION ==============================================================================================\n\t/** Default width of the video block. Can be specified in %, px */\n\tpublic $default_width\t\t\t= '100%';\n\t/** Default aspect ratio of the video block. Syntax X:X or X/X */\n\tpublic $default_aspect_ratio\t= '16/9';\n\t/** GDPR notice text in different languages. */\n\tprivate $default_gdpr_text \t\t= [  \n\t\t'da' => ['Når du har trykket, vil videoen blive indlæst fra Vimeo\\'s servere. Se vores %s for flere informationer.','privatlivspolitik'],\n\t\t'de' => ['Bei Klick wird dieses Video von den Vimeo Servern geladen. Details siehe %s.', 'Datenschutzerklärung'],\n\t\t'en' => ['When clicked, this video is loaded from Vimeo servers. See our %s for details.', 'privacy policy'],\n\t\t'es' => ['Al hacer clic, este vídeo se carga desde los servidores de Vimeo. Consulte la %s para más detalles.', 'política de privacidad'],\n\t\t'fi' => ['Klikattuasi, tämä video ladataan Vimeon palvelimilta. Katso lisätietoja meidän %s.', 'tietosuojaselosteesta'],\n\t\t'fr' => ['En cliquant, cette vidéo est chargée depuis les serveurs de Vimeo. Voir la %s.', 'politique de confidentialité'], \n\t\t'hu' => ['Kattintás után ez a videó a Vimeo szervereiről kerül lejátszásra. A részletekért olvassa el az %s oldalt.', 'Adatkezelési Tájékoztatót'],\n\t\t'it' => ['Quando si clicca, questo video viene caricato dai server di Vimeo. Vedere %s per i dettagli.', 'l\\'informativa sulla privacy'],\n\t\t'ja' => ['クリックすると、この動画が Vimeo サーバーから読み込まれます。詳細については、%s をご覧ください。', 'プライバシー ポリシー'],\n\t]; \n\t/** Default font size for GDPR text */\n\tpublic $default_gdpr_text_size\t= '.7em';\n\t/** Default open video in new window */\n\tprivate $default_new_window\t\t= false;\n\t/** thumbnail image sizes */\n\tprivate $thumbnail_image_sizes\t= [1280,1024,960,768,640,480]; \n\t/** Enable timing info to WordPress debug.log if WP_DEBUG also enabled. \n\t * - false/0:\tDisabled \n\t * - true/1: \tEnabled \n\t * - 2: \t\tExtended \n\t */\n\tpublic $timing\t\t\t\t\t= false;\n\n\t// ===== INTERNAL. DO NOT EDIT. =====================================================================================\n\tprivate $incompatibilities \t\t= [];\t\t// incompatibilities detected before initialization\n\tprivate $content_base\t\t\t= null;\t\t// will be set to the content base folder dir and url\n\tprivate $footercode_needed\t\t= false;\t// will be set to true if shortcode used on current page\n\tprivate $footercode_minimize \t= true;\t\t// should we minimize all footer code (style, script, svg)?\n\tprivate $timing_total_runtime\t= 0;\n\tprivate $urlformat_oembed\t\t= 'https://vimeo.com/api/oembed.json?url=https%%3A//vimeo.com/%s';\n\tprivate $urlformat_simpleapi\t= 'http://vimeo.com/api/v2/video/%s.json';\n\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Initialize Snippet: \n\t * - Add shortcode \"ma-gdpr-youtube\"\n\t * - Register hook action for wp_footer to emit footer code (style, script)\n\t * - Set flag to emit footer code (style, script) when in Oxygen Builder\n\t */\n\tfunction __construct() {\n\t\t$st = microtime(true);\n\t\t$GLOBALS[__CLASS__] = $this;\n\n\t\tif (wp_doing_cron()) \t\tgoto DONE;\t// don't run for CRON requests\n\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s Initializing...',__CLASS__));}\n\n\t\tif (!defined('MA_GDPR_Vimeo_Version')) define('MA_GDPR_Vimeo_Version',self::VERSION);\n\n\t\tif ($this->is_incompatible()) return;\n\n\t\t// check content directory\n\t\t$this->content_base = $this->get_content_base();\n\t\tif (!$this->content_base) {return;}\n\n\t\tadd_shortcode('ma-gdpr-vimeo', [$this, 'shortcode']);\n\t\tadd_action('wp_footer',[$this,'footercode']);\n\n\t\tadd_action('init', [$this, 'init_builder'], 50);\n\n\t\tDONE:\n\t\t// add a handler for logging total runtime\n\t\tadd_action('shutdown', [$this, 'total_runtime']);\n\t\t\n\t\t$et = microtime(true);\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('  %s Initialization Timing %.5f s.',__CLASS__, $et-$st));}\n\t\t$this->timing_total_runtime += $et-$st;\n\n\t\t$this->timing_total_runtime += $et-$st;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Logs total timing for shortcodes on a page \n\t */\n\tpublic function total_runtime(){\n\t\tif (WP_DEBUG && $this->timing) {error_log(sprintf('%s Runtime: %.5f sec.', __CLASS__, $this->timing_total_runtime));}\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Checks for incompatibilities. Registers admin notice.\n\t * @return bool \t\t\t\t`true` if any incompatibilities found\n\t */\n\tprivate function is_incompatible(): bool{\n\t\t$incomp = [];\n\t\tif (!ini_get('allow_url_fopen')) {\n\t\t\t$incomp[] = 'PHP setting <code>allow_url_fopen</code> needs to be <b><On</b> for the '.$this->get_script_details()->type.' to work correctly.';\n\t\t}\n\t\t$this->incompatibilities = $incomp;\n\t\tif (count($incomp) && is_admin()) {\n\t\t\tadd_action('admin_notices', function(){\n\t\t\t\tif (WP_DEBUG ) {error_log(self::TITLE.' Incompatibilities: '.implode(', ',$this->incompatibilities));}\n\t\t\t\techo '<div class=\"notice notice-error is-dismissible\">\n\t\t\t\t\t\t<p>The '.$this->get_script_details()->combined.' is skipped: '.implode(', ',$this->incompatibilities).'</p>\n\t\t\t\t\t</div>';\n\t\t\t});\n\t\t}\t\n\t\treturn count($incomp) ? true : false;\t\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Init actions for Builder support.\n\t */\n\tpublic function init_builder() {\n\t\t\n\t\t// OXYGEN\n\t\tif ( ($_GET['ct_builder']??null) == true) {\n\t\t\t// emit styles, script, svg when Oxygen Builder is active to support video preview\n\t\t\t$this->footercode_needed = true;\n\t\t}\n\t\t// BRICKS\n\t\tif (defined('BRICKS_VERSION')) {\n\t\t\tif ( ($_GET['bricks']??null) == 'run') {\n\t\t\t\t// emit styles, script, svg when Bricks Builder is active to support video preview\n\t\t\t\t$this->footercode_needed = true;\n\t\t\t}\n\t\t\t// load Bricks element add-on (not yet available)\n\t\t\tforeach([__DIR__,$this->content_base->dir] as $module_dir) {\n\t\t\t\t$module_filepath = $module_dir.'/'.self::SLUG.'-bricks-element.php';\n\t\t\t\tif (file_exists($module_filepath)) {\n\t\t\t\t\tcall_user_func('\\Bricks\\Elements::register_element', $module_filepath);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Return base dir/url for video content. \n\t * Create directory /wp-content/uploads/ma-gdpr-vimeo/ if necessary\n\t * Rename from old scheme ma-gdpr-vimeo-thumbnails if exists\n\t * @return object|null\tA dirinfo object (->dir, ->url)\n\t */\n\tpublic function get_content_base(): ?object {\n\t\t$retval = (object)['dir'=>null,'url'=>''];\n\t\t$content_dir_info = wp_get_upload_dir();\n\t\t$retval->dir = $content_dir_info['basedir'].'/'.self::SLUG;\n\t\t$retval->url = $content_dir_info['baseurl'].'/'.self::SLUG;\n\t\t// rename folder from old to new scheme \n\t\tif (file_exists($retval->dir.'-thumbnails')) {\n\t\t\t@rename($retval->dir.'-thumbnails',$retval->dir);\n\t\t}\n\t\t// create content folder if not exists\n\t\tif (!file_exists($retval->dir)) {\n\t\t\tif (!@mkdir($retval->dir)) {\n\t\t\t\tadd_action('admin_notices', function(){\n\t\t\t\t\techo '<div class=\"notice notice-error\"><p>['.self::TITLE.'] Error creating content base folder <code>wp-content/uploads/'.self::SLUG.'</code>.</p></div>';\n\t\t\t\t});\n\t\t\t\terror_log(sprintf('%s Error creating content base folder.', __CLASS__)); \n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\tif (!is_writable($retval->dir)) {\n\t\t\tadd_action('admin_notices', function(){\n\t\t\t\techo '<div class=\"notice notice-error\"><p>['.self::TITLE.'] Folder <code>wp-content/uploads/'.self::SLUG.'</code> is not writable. Please correct folder permissions.</p></div>';\n\t\t\t});\n\t\t}\n\t\t// create scheme-less URL\n\t\t$retval->url = preg_replace('/^https?\\:/','',$retval->url);\n\t\treturn $retval;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Return a link to the privacy policy page (if configured in WordPress) or just the passed text\n\t * @param string $text\tThe text to return if privacy policy page is not defined in WordPress\n\t * @return string\t\tThe HTML link element for the privacy policy page or the initial text\n\t */\n\tprivate function get_privacy_policy_link(string $text='privacy policy'): string {\n\t\t$pplink = get_the_privacy_policy_link();\n\t\treturn  $pplink ? $pplink : $text; \n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Parses a Vimeo URL for a video ID.\n\t * Handles numerous URL formats, or plain video ID.\n\t * @param string $s\t\tThe URL/string to parse\n\t * @param string $h \tThe optional hash key for hidden videos\n\t * @return array\t\tArray containing key v (video ID), or empty\n\t */\n\tprivate function parse_video_url(string $s='', $h=''): array {\n\t\t$st = microtime(true);\n\t\t$retval = ['v'=>null, 'h'=>$h];\n\t\t// regex for parsing vimeo url variants\n\t\t$re = '/^\n\t\t\t(?:https?\\:)?\\/{2}\t\t\t\t\t# protocol http, https, or schemeless\n\t\t\t(?:www\\.)?\t\t\t\t\t\t\t# optional www\n\t\t\t(?:(?:player\\.)?vimeo\\.com)\\/\t\t# domain variants\n\t\t\t(?:(?:video)[\\/]?) ?\t\t\t\t# video term\n\t\t\t([A-Za-z0-9\\-\\_]+)\t\t\t\t\t# THE ID\n\t\t\t(?:\\/([A-Za-z0-9]+)) ?\t\t\t\t# (optional) HASH key as path appending\n\t\t\t(?:[.+\\?\\&]h=([A-Za-z0-9]+)) ?\t\t# (optional) HASH key as URL parameter\n\t\t/x';\n\t\t// parse url\n\t\tif (preg_match($re,$s,$matches)) {\n\t\t\t$retval['v'] = $matches[1]; \n\t\t\t$retval['h'] = ($matches[2]??null) ? $matches[2] : ($matches[3]??null);\n\t\t}\n\t\t// id only?\n\t\telse if (preg_match('/^([A-Za-z0-9\\-\\_]+)$/',$s,$matches))\t{$retval['v'] = $matches[1];}\n\t\n\t\t$et = microtime(true);\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('    %s(\"%s\") => %s Timing: %.5f sec.', __METHOD__, $s, json_encode($retval), $et-$st));}\n\t\treturn $retval;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/** \n\t * Get the image type as extension string from an integer value. Handles gif, jpg, pmg, webp.\n\t * @param int $image_type \tThe image type as int \n\t * @return string \t\t\tThe image extension\n\t */\n\tprivate function get_image_type_as_string(int $image_type): string {\n\t\tswitch ($image_type) {\n\t\t\tcase IMAGETYPE_GIF: \treturn 'gif';\n\t\t\tcase IMAGETYPE_JPEG:\treturn 'jpg';\n\t\t\tcase IMAGETYPE_PNG:\t\treturn 'png';\n\t\t\tcase IMAGETYPE_WEBP:\treturn 'webp';\n\t\t\tdefault:\t\t\t\treturn '';\n\t\t}\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Get type, width, height from an image file.\n\t * @param string $filepath \tThe image file path\n\t * @return object \t\t\tThe image info object: (bool) status, (string) type, (int) width, (int) height\n\t */\n\tprivate function get_image_info(string $filepath): object {\n\t\t$retval = (object)[\n\t\t\t'status'\t=> false,\n\t\t\t'type'\t\t=> null,\n\t\t\t'width'\t\t=> null,\n\t\t\t'height'\t=> null,\n\t\t];\n\t\tif ($result = getimagesize($filepath)) {\n\t\t\t$retval->status = true;\n\t\t\t$retval->type \t= $this->get_image_type_as_string($result[2]);\n\t\t\t$retval->width\t= $result[0];\n\t\t\t$retval->height\t= $result[1];\n\t\t}\n\t\treturn $retval;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Parses some info from an vimeo embed request result\n\t * @param object $data\tThe embed request result\n\t * @return object \t\tThe info parsed \n\t */\n\tprivate function parse_embed_info(object $data): object {\n\t\t$retval = (object)[\n\t\t\t'hash' \t\t\t=> null,\n\t\t\t'app_id'\t\t=> null,\n\t\t\t'title'\t\t\t=> null,\n\t\t];\n\t\t$html = $data->html??'';\n\t\t/* Sample: \n\t\t<iframe src=\"https://player.vimeo.com/video/347119375?h=1699409fe2&amp;app_id=122963&autoplay=1&color=ef2200&byline=0&portrait=0\" \n\t\t\twidth=\"640\" height=\"360\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen title=\"Sample Video\"></iframe>\n\t\t*/\n\t\tif (preg_match('/[\\?&;]h=([\\da-f]+)/',$html,$matches)) \t\t{$retval->hash = $matches[1];}\n\t\tif (preg_match('/[\\?&;]app_id=([\\da-f]+)/',$html,$matches))\t{$retval->app_id = $matches[1];}\n\t\tif (preg_match('/title=\"([^\"]+)\"/',$html,$matches)) \t\t{$retval->title = html_entity_decode($matches[1]);}\n\n\t\tif (!$retval->hash) {\n\t\t\t// if URL doesn't conatain the hash key, try to parse from uri (/videos/12345 or /videos/12345:67890)\n\t\t\tif (preg_match('/^\\/videos\\/[A-Za-z0-9]+\\:([A-Za-z0-9]+)$/',$data->uri??'',$matches)) {\n\t\t\t\t$retval->hash = $matches[1];\n\t\t\t}\n\t\t}\n\n\t\treturn $retval;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Get details for video $id, containing hash, app_id, title, thumbnails, if already downloaded and cached. \n\t * Load from Vimeo if not yet cached or old version (pre 1.4.0).\n\t * @param string $id\tThe video ID\n\t * @param mixed  $hash \tThe optional hash key for hidden videos\n\t * @return object\t\tObject with status, title, hash, app_id and thumbnails\n\t */\n\tprivate function get_video_details(string $id, $hash=null): object {\n\t\t$st = microtime(true);\n\t\t$retval = (object)[\n\t\t\t'status' \t\t=> false,\n\t\t\t'status_code'\t=> null,\n\t\t\t'status_text'\t=> null,\n\t\t\t'status_errors'\t=> [],\n\t\t\t'json_version'\t=> 2, \n\t\t\t'video_id'\t\t=> $id,\n\t\t\t'hash'\t\t\t=> null,\n\t\t\t'app_id'\t\t=> null,\n\t\t\t'embed'\t\t\t=> null,\n\t\t\t'thumbnails'\t=> (object)[],\n\t\t];\n\t\tif (!$this->content_base) \t{goto DONE;}\n\n\t\t$vid_dir = $this->content_base->dir.'/'.$id;\n\t\t$info_filepath = $vid_dir.'/'.$id.'.json';\n\n\t\t// read from cache if available\n\t\tif (file_exists($info_filepath)) {\n\t\t\t// read from cache\n\t\t\t$result = json_decode(@file_get_contents($info_filepath));\n\t\t\t// json_version 2, status OK, and thumbnails available?\n\t\t\tif ($result && ($result->json_version??1>=2) && ($result->status??null) && ($result->thumbnails??[])) {\n\t\t\t\t$retval = $result;\n\t\t\t\tgoto DONE;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// not cached yet, or old cache format; load from vimeo\n\t\tLOAD_FROM_VIMEO:\n\n\t\t// prepare vimeo calls\n\t\t$referer = ($_SERVER['HTTPS']??'' == 'on' ? 'https:' : 'http').'//'.($_SERVER['HTTP_HOST']??'');\n\t\t$http_request_headers = [\n\t\t\t'Accept-language: '.$this->get_current_language(),\n\t\t\t'Referer: '.$referer,\n\t\t];\n\t\t$options = ['http'=>['method'=>'GET','header'=>implode(\"\\r\\n\",$http_request_headers)]];\n\t\t$context = stream_context_create($options);\n\n\t\t$base_url = sprintf($this->urlformat_oembed, $id);\n\t\tif ($hash) $base_url .= '/'.$hash; // optional hash key\n\n\t\t// retrieve from vimeo in a loop: embed data, thumbnail urls\n\t\tforeach ($this->thumbnail_image_sizes as $width) {\n\t\t\t$st_thumbnail = microtime(true);\n\t\t\t$url = $base_url . '&width='.$width;\n\t\t\t$result = @file_get_contents($url, false, $context);\n\t\t\t\n\t\t\t// check status\n\t\t\t$http_status = $http_response_header[0];\n\t\t\tpreg_match( \"#HTTP/[0-9\\.]+\\s+([0-9]+)\\s(.*)#\",$http_status, $matches);\n\t\t\t$retval->status_code = $matches[1];\n\t\t\t$retval->status_text = trim($matches[2]);\n\t\t\tif ($retval->status_code == 200) {\n\t\t\t\t$data = json_decode($result); \n\t\t\t\tif (!$retval->embed) $retval->embed = $data;\n\t\t\t\t// parse embed info\n\t\t\t\t$parsed = $this->parse_embed_info($data);\n\t\t\t\t$retval->hash = $parsed->hash;\n\t\t\t\t$retval->app_id = $parsed->app_id;\n\n\t\t\t\tif (($data->domain_status_code??null) == 403) {\n\t\t\t\t\t$retval->status_code = 403;\n\t\t\t\t\t$retval->status_text = 'Forbidden';\n\t\t\t\t}\n\t\t\t\tif (($data->domain_status_code??null) == 404) {\n\t\t\t\t\t$retval->status_code = 404;\n\t\t\t\t\t$retval->status_text = 'Not Found';\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ($retval->status_code != 200) {\n\t\t\t\t// cancel retrieval if video not found or not accessible\n\t\t\t\t$note = sprintf('Video %s: %s', $id, $retval->status_text);\n\t\t\t\t$retval->status_errors[] = $note;\n\t\t\t\terror_log('['.self::TITLE.'] '.$note); \n\t\t\t\tgoto DONE;\n\t\t\t}\n\n\t\t\t// video is accessible; check/create cache directory for this video\n\t\t\tif (!file_exists($vid_dir)) {\n\t\t\t\tif (!@mkdir($vid_dir)) {\n\t\t\t\t\t// cancel retrieval if cache directory can't be created\n\t\t\t\t\t$note = sprintf('Error creating cache folder for video %s.', $id);\n\t\t\t\t\t$retval->status_errors[] = $note;\n\t\t\t\t\terror_log('['.self::TITLE.'] '.$note); \n\t\t\t\t\tgoto DONE;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// get thumbnail info\n\t\t\t$thumbnail = (object)[\n\t\t\t\t'url' => $data->thumbnail_url,\n\t\t\t\t'width' => $data->thumbnail_width,\n\t\t\t\t'height' => $data->thumbnail_height,\n\t\t\t];\n\t\t\t// load and store thumbnail\n\t\t\tif ($img_data = @file_get_contents($thumbnail->url)) {\n\t\t\t\t$status = $http_response_header[0];\n\t\t\t\tif (!preg_match('/200\\sOK/',$status)) continue; // continue with next image size\n\t\t\t\t// store as tmp file to get image type from exif\n\t\t\t\t$img_tmp_path = $vid_dir.'/'.$id.'.tmp';\n\t\t\t\tfile_put_contents($img_tmp_path, $img_data);\n\t\t\t\t$img_info = $this->get_image_info($img_tmp_path);\n\t\t\t\tif ($img_info->status) {\n\t\t\t\t\t$img_path = $vid_dir.'/'.$id.'_'.$img_info->width.'.'.$img_info->type;\n\t\t\t\t\t// store with correct filename\n\t\t\t\t\tif (@rename($img_tmp_path, $img_path)) {\n\t\t\t\t\t\t$retval->thumbnails->{$img_info->width} = (object)['name'=>basename($img_path), 'width'=>$img_info->width, 'height'=>$img_info->height];\n\t\t\t\t\t}\n\t\t\t\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('      Thumbnail %d px loaded, %d px received. Timing: %.5f sec.', $width, $img_info->width, microtime(true)-$st_thumbnail));}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t$retval->status = true;\n\n\t\tDONE:\n\t\t// clean up embed info\n\t\tunset($retval->embed->thumbnail_url);\n\t\tunset($retval->embed->thumbnail_width);\n\t\tunset($retval->embed->thumbnail_height);\n\t\tunset($retval->embed->thumbnail_url_with_play_button);\n\t\t// store cache file\n\t\t@file_put_contents($info_filepath, json_encode($retval,JSON_PRETTY_PRINT));\n\n\t\t$et = microtime(true);\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('    %s(\"%s\") Timing: %.5f sec.', __METHOD__, $id, $et-$st));}\n\t\treturn $retval;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Get the current page/post get_current_language\n\t * @return string\t\tThe language code as e.g. \"de\", \"en\"\n\t */\n\tprivate function get_current_language(): string {\n\t\t$retval = get_locale();\n\t\t// Is a translation plugin active? Supporting Polylang, WPML \n\t\tforeach (['pll_current_language','wpml_current_language'] as $func) {\n\t\t\tif (function_exists($func)) {$retval = $func(); break;}\n\t\t}\n\t\t$retval = str_replace('_','-',$retval);\n\t\t$retval = explode('-',$retval??'')[0];\n\t\treturn $retval;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Handle the shortcode \"ma-gdpr-vimeo\". \n\t * @param array $atts\t\tThe shortcode attributes\n\t * @param string $content\tThe content of the shortcode\n\t * @return string\t\t\tThe output\n\t */\n\tpublic function shortcode(array $shortcode_atts = [], string $content = ''): string {\n\t\t$st = microtime(true);\n\t\t$lang = $this->get_current_language();\n\t\t$retval = '';\n\n\t\t// get defaults for unspecified attributes\n\t\t$atts_default = [\n\t\t\t'slug'\t\t\t\t=> self::SLUG,\n\t\t\t'video'\t\t\t\t=> null,\n\t\t\t'h' \t\t\t\t=> null, // the hidden key\n\t\t\t'uniqid'\t\t\t=> null,\n\t\t\t'width'\t\t\t\t=> $this->default_width,\n\t\t\t'aspect-ratio'\t\t=> $this->default_aspect_ratio,\n\t\t\t'notice-class'\t\t=> null,\n\t\t\t'notice-style'\t\t=> null,\n\t\t\t'gdpr-text'\t\t\t=> isset($this->default_gdpr_text[$lang]) \n\t\t\t\t\t\t\t\t\t? sprintf($this->default_gdpr_text[$lang][0],$this->get_privacy_policy_link($this->default_gdpr_text[$lang][1])) \n\t\t\t\t\t\t\t\t\t: sprintf($this->default_gdpr_text['en'][0],$this->get_privacy_policy_link($this->default_gdpr_text['en'][1])),\n\t\t\t'gdpr-text-size'\t=> null,\n\t\t\t'alt'\t\t\t\t=> null,\n\t\t\t'title'\t\t\t\t=> null,\n\t\t\t'thumbnail'\t\t\t=> null,\n\t\t\t'title-text'\t\t=> null,\n\t\t\t'title-class'\t\t=> null,\n\t\t\t'title-style'\t\t=> null,\n\t\t\t'play-button'\t\t=> 'vimeo', // currently included: vimeo, circle, circle-o, play\n\t\t\t'play-button-style'\t=> null,\n\t\t\t'play-button-color'\t=> null,\n\t\t\t'new-window'\t\t=> $this->default_new_window,\n\t\t];\n\n\t\t// merge global settings\n\t\t$atts = array_merge($atts_default, $GLOBALS['ma_gdpr_vimeo']??[]);\n\t\t// choose correct language for gdpr-text\n\t\tif ($GLOBALS['ma_gdpr_vimeo']['gdpr-text-'.strtolower($lang)]??'') {\n\t\t\t$atts['gdpr-text'] = $GLOBALS['ma_gdpr_vimeo']['gdpr-text-'.strtolower($lang)];\n\t\t}\n\t\t// allow html in shortcode attributes title-text, gdpr-text\n\t\tforeach (['title-text','gdpr-text'] as $att) {\n\t\t\tif ($shortcode_atts[$att]??'') {$shortcode_atts[$att] = html_entity_decode($shortcode_atts[$att]);}\n\t\t}\n\t\t// merge shortcode attributes\n\t\t$atts = (object)array_merge($atts, $shortcode_atts);\n\t\tif ($atts->video) {\n\t\t\t$video = $this->parse_video_url($atts->video, $atts->h);\n\t\t\tif (isset($video['v'])) {\n\t\t\t\t$atts->video = $video['v'];\n\t\t\t\t// use h key found in URL only if no manual h parameter specified\n\t\t\t\tif (!$atts->h) {$atts->h = $video['h']??null;}\n\t\t\t}\n\t\t}\n\n\t\t// any other parameter will be passed to vimeo directly\n\t\t$vimeo_parameters = [];\n\t\tforeach ($atts as $att_key => $att_val) {\n\t\t\tif (!in_array($att_key,array_keys($atts_default))) {\n\t\t\t\t$vimeo_parameters[$att_key] = $att_val;\n\t\t\t} \n\t\t}\n\n\t\tif (!isset($atts->video) || ($atts->video == '' )) \t{$retval = sprintf('[%s] Missing video id.',self::TITLE); goto DONE;}\n\t\tif (preg_match('/[^A-Za-z0-9\\-\\_]/',$atts->video))\t{$retval = sprintf('[%s] Invalid video id.',self::TITLE); goto DONE;}\n\t\t// generate an unique id (for the case a video is embedded multiple times)\n\t\t$atts->uniqid = $atts->slug.'-'.uniqid();\n\n\t\tif (!$this->content_base) {$retval = sprintf('[%s] Content directory is not available.',self::TITLE); goto DONE;}\n\t\t// check if we already have a thumbnail\n\t\t$video_details = $this->get_video_details($atts->video, $atts->h);\n\t\tif (!$video_details->status) {$retval = sprintf('[%s] %s',self::TITLE, implode(', ',$video_details->status_errors)); goto DONE;}\n\n\t\t// insert vimeo attributes\n\t\tif ($video_details->status) {\n\t\t\tforeach (['alt','title','title-text'] as $attr) {\n\t\t\t\tforeach (['title','author_name','description'] as $info) {\n\t\t\t\t\t$atts->{$attr} = str_replace('@'.$info.'@', $video_details->embed->{$info}??'', $atts->{$attr}??'');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t$thumbnail = '';\n\t\t$sources = [];\n\t\t$click_handler = 'onclick=\"ma_gdpr_vimeo.click(this)\"';\n\n\t\tif ($atts->thumbnail) {\n\t\t\tif (is_numeric($atts->thumbnail)) {\n\t\t\t\t// numeric value => media id\n\t\t\t\t// get available image sizes\n\t\t\t\t$metadata = wp_get_attachment_metadata($atts->thumbnail);\n\t\t\t\t$image_sizes = [];\n\t\t\t\t// add original size\n\t\t\t\t$img_src = wp_get_attachment_image_src($atts->thumbnail,'full');\t// '0': url, '1': width, '2': height, '3': resized\n\t\t\t\t$mime_type = get_post_mime_type($atts->thumbnail);\n\t\t\t\t$image_sizes['original'] = [\n\t\t\t\t\t'file'\t\t=> $metadata['file'],\n\t\t\t\t\t'width'\t\t=> $metadata['width'],\n\t\t\t\t\t'height'\t=> $metadata['height'],\n\t\t\t\t\t'mime-type'\t=> $mime_type,\n\t\t\t\t\t'filesize'\t=> $metadata['filesize']??null,\n\t\t\t\t\t'url'\t\t=> $img_src[0],\n\t\t\t\t\t'key'\t\t=> 'full',\n\t\t\t\t];\n\t\t\t\t// add resized sizes\n\t\t\t\tforeach ($metadata['sizes'] as $key => $data) {\n\t\t\t\t\t// retrieve url for specific size\n\t\t\t\t\t$img_src = wp_get_attachment_image_src($atts->thumbnail,$key);\t// '0': url, '1': width, '2': height, '3': resized\n\t\t\t\t\t$data['url'] = $img_src['0']; \n\t\t\t\t\t$data['key'] = $key;\n\t\t\t\t\t$image_sizes[$key] = $data;\n\t\t\t\t}\n\t\t\t\t// sort image sizes by width ascending\n\t\t\t\tuasort($image_sizes, function($a,$b){\n\t\t\t\t\tif ($a['width'] == $b['width']) {return 0;}\n\t\t\t\t\treturn ($a['width'] < $b['width']) ? -1 : 1;\n\t\t\t\t});\n\n\t\t\t\t// get largest image \n\t\t\t\t$largest = (object)end($image_sizes);\n\n\t\t\t\tforeach ($image_sizes as $key => $data) {\n\t\t\t\t\t// to improve thumbnail quality, use higher res image if we reach half its size \n\t\t\t\t\t$sources[] = sprintf('<source media=\"(min-width:%dpx)\" type=\"%s\" srcset=\"%s\" width=\"%d\" height=\"%d\">',\n\t\t\t\t\t\t\t\t\t\t$data['width']/2, $data['mime-type'], $data['url'], $data['width'], $data['height']);\n\t\t\t\t}\n\t\t\t\t// create thumbnail\n\t\t\t\t$thumbnail .= sprintf('<picture class=\"ma-gdpr-vimeo-thumbnail\" '.$click_handler.'>%s <img loading=\"lazy\" src=\"%s\" width=\"%d\" height=\"%d\" alt=\"%s\" title=\"%s\"></picture>',\n\t\t\t\t\t\t\t\t\timplode('',array_reverse($sources)), $largest->url, $largest->width, $largest->height, $atts->alt??'', $atts->title??'');\n\t\t\t} else {\n\t\t\t\t// thumbnail URL\n\t\t\t\t// no width and height attributes because URL could be external, and retrieving img sizes would require extra request\n\t\t\t\t$sources = [];\n\t\t\t\t$thumbnail .= '<picture class=\"ma-gdpr-vimeo-thumbnail\" '.$click_handler.'>';\n\t\t\t\t$thumbnail .= sprintf('<img loading=\"lazy\" src=\"%s\" alt=\"%s\" title=\"%s\">',$atts->thumbnail, $atts->alt, $atts->title);\n\t\t\t\t$thumbnail .= '</picture>';\n\t\t\t}\n\t\t} else {\n\t\t\t$thumbs = $video_details->thumbnails;\n\t\t\tforeach(get_object_vars($thumbs) as $size => $thumb) {\n\t\t\t\t$type = substr(strrchr($thumb->name,'.'),1);\n\t\t\t\t// to improve thumbnail quality, use higher res image if we reach half its size \n\t\t\t\t$sources[] = sprintf('<source media=\"(min-width:%dpx)\" type=\"image/%s\" srcset=\"%s\" width=\"%d\" height=\"%d\">',\n\t\t\t($size/2), $type, $this->content_base->url.'/'.$atts->video.'/'.$thumb->name, $thumb->width, $thumb->height);\n\t\t\t}\n\t\t\t// get smallest (last) thumbnail as img src\n\t\t\t$thumbs = (array)$thumbs;\n\t\t\t$smallest = array_pop($thumbs);\n\t\t\t$img_src = $this->content_base->url.'/'.$atts->video.'/'.$smallest->name;\n\t\t\t$thumbnail .= sprintf('<picture id=\"%s-thumbnail'.'\" class=\"ma-gdpr-vimeo-thumbnail\" %s> %s <img src=\"%s\" alt=\"%s\" title=\"%s\" width=\"%d\" height=\"%d\"></picture>',\n\t\t\t\t\t\t\t\t$atts->uniqid, $click_handler, implode('',$sources), $img_src, $atts->alt, $atts->title, $smallest->width, $smallest->height);\n\t\t}\n\n\t\t// calculate dimensions of video block depending on width and aspect ratio\n\t\tlist ($arw,$arh) = explode('/',str_replace(':','/',$atts->{'aspect-ratio'}),2); // aspect ratio elements\n\t\tlist ($width_value, $width_unit) = ['100','%']; // default width value and unit\n\t\t// split width value and unit\n\t\tpreg_match('/^(\\d+)(.+)$/',$atts->width,$matches);\n\t\tif (count($matches) == 3) {array_shift($matches); list ($width_value, $width_unit) =  $matches;}\n\t\t// calculate block dimensions\n\t\t$block_width = $width_value.$width_unit;\n\t\t$block_height = ($width_value * (floatval($arh)/floatval($arw))) . $width_unit;\n\n\t\t// privacy policy url and link\n\t\t$atts->{'gdpr-text'} = str_replace('{privacy-policy-url}', get_privacy_policy_url(), $atts->{'gdpr-text'});\n\t\t$atts->{'gdpr-text'} = str_replace('{privacy-policy-link}', get_the_privacy_policy_link(), $atts->{'gdpr-text'});\n\n\t\t// title overlay\n\t\t$title_overlay = !empty($atts->{'title-text'})\n\t\t\t? sprintf('<div class=\"ma-gdpr-vimeo-title %1$s\" %2$s>%3$s</div>',\n\t\t\t\t$atts->{'title-class'} ?? '',\n\t\t\t\t$atts->{'title-style'} ? 'style=\"'.$atts->{'title-style'}.'\"' : '',\n\t\t\t\t$atts->{'title-text'}\n\t\t\t\t)\n\t\t\t: '';\n\n\t\t// play button style, color\n\t\t$play_button_style = '';\n\t\tif ($atts->{'play-button-style'}) {$play_button_style .= $atts->{'play-button-style'}.';';}\n\t\tif ($atts->{'play-button-color'}) {$play_button_style .= 'color:'.$atts->{'play-button-color'}.';';}\n\t\tif ($play_button_style) {$play_button_style = 'style=\"'.$play_button_style.'\"';}\n\n\t\t// gdpr text size\n\t\t$gdpr_text_size = '';\n\t\tif ($atts->{'gdpr-text-size'}) {$gdpr_text_size = 'font-size:'.$atts->{'gdpr-text-size'}.';';}\n\n\t\t// vimeo iframe\n\t\t$content = <<<END_OF_CONTENT\n\t\t<iframe src=\"https://player.vimeo.com/video/{$video_details->video_id}?h={$video_details->hash}&app_id={$video_details->app_id}&autoplay=0&dnt=1\" \n\t\t\tstyle=\"position:absolute;top:0;left:0;width:100%;height:100%;\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen></iframe>\n\t\t<script src=\"https://player.vimeo.com/api/player.js\"></script>\nEND_OF_CONTENT;\n\n\n\t\t$aspect_ratio\t\t= str_replace(':','/',$atts->{'aspect-ratio'});\n\t\t$new_window\t\t\t= $atts->{'new-window'};\n\t\t$vimeo_params\t\t= count($vimeo_parameters) ? base64_encode(json_encode($vimeo_parameters)) : '';\n\t\t$play_button \t\t= $atts->{'play-button'} ?? '';\n\t\t$play_button_class\t= $atts->{'play-button-class'} ?? '';\n\t\t$notice_class\t\t= $atts->{'notice-class'} ?? '';\n\t\t$notice_style\t\t= $atts->{'notice-style'} ?? '';\n\t\t$gdpr_text\t\t\t= $atts->{'gdpr-text'};\n\t\t$content_b64\t\t= base64_encode($content);\n\n\t\t// @since 1.2.0 width/height/aspect-ratio now set via CSS var for older browsers not supporting aspect-ratio, like e.g. Safari <V15\n\t\t$retval = <<<END_OF_HTML\n\t\t<div id=\"{$atts->uniqid}\" data-video-id=\"{$atts->video}\" class=\"ma-gdpr-vimeo-wrapper\" style=\"--_width:{$block_width};--_height:{$block_height};--_aspect-ratio:{$aspect_ratio};\" data-new-window=\"{$new_window}\" data-parameters=\"{$vimeo_params}\">\n\t\t\t{$thumbnail}\n\t\t\t<svg id=\"{$atts->uniqid}-button\" class=\"ma-gdpr-vimeo-button button-{$play_button} {$play_button_class}\" tabindex=\"0\" role=\"button\" aria-label=\"play video\" {$play_button_style} {$click_handler}><use xlink:href=\"#ma-gdpr-vimeo-play-button-{$play_button}\"></use></svg>\n\t\t\t{$title_overlay}\n\t\t\t<div class=\"ma-gdpr-vimeo-notice {$notice_class}\" style=\"font-size:{$gdpr_text_size}; {$notice_style}\">{$gdpr_text}</div>\n\t\t\t<div id=\"{$atts->uniqid}-content\" class=\"ma-gdpr-vimeo-content\" style=\"display:none\">{$content_b64}</div>\n\t\t</div>\nEND_OF_HTML;\n\t\t$this->footercode_needed = true;\n\n\t\tDONE:\n\t\t$et = microtime(true);\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('  %s(%s) Timing: %.5f sec.', __METHOD__, json_encode($shortcode_atts), $et-$st));}\n\t\t$this->timing_total_runtime += $et-$st;\n\t\treturn $retval;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Enables emission of footercode (styles, scripts, svg) for the video embed. \n\t * Can be used for pages where video is embedded dynamically by an AJAX call.\n\t * On the parent page, call MA_GDPR_Vimeo::enable_footercode()\n\t * @since 1.2.0\n\t */\n\tpublic static function enable_footercode() {\n\t\t$GLOBALS[__CLASS__]->footercode_needed = true;\n\t}\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Emits the footer code (styles, script, svg) to handle the Vimeo embedding\n\t */\n\tpublic function footercode() {\n\t\t$st = microtime(true);\n\n\t\tif (!$this->footercode_needed) {goto DONE;}\n\n\t\t// debugging info\n\t\techo sprintf('<span id=\"%2$s-info\" data-nosnippet style=\"display:none\">%1$s %2$s %3$s</span>', $this->get_script_details()->type, self::SLUG, self::VERSION); \n\t\t\n\t\t// emit style\n\t\t// @since 1.2.0: @supports rule provides wrapper height for older browsers not supporting aspect-ratio, like e.g. Safari <V15\n\t\t$style = <<<'END_OF_STYLE'\n\t\t<style id=\"ma-gdpr-vimeo-style\">\n\t\t\t.ma-gdpr-vimeo-wrapper {position:relative; display:flex; isolation:isolate; width:var(--_width);aspect-ratio:var(--_aspect-ratio);}\n\t\t\t@supports not (aspect-ratio:1/1) {.ma-gdpr-vimeo-wrapper{height:var(--_height);padding-top:var(--_height);}}\n\t\t\t.ma-gdpr-vimeo-thumbnail {position:absolute; z-index:1; top:0; left:0; width:100%; height:100%; display:flex; cursor:pointer; }\n\t\t\t.ma-gdpr-vimeo-thumbnail img {width:100%; height:100%; object-fit:cover; object-position:50% 50%;}\n\t\t\t.ma-gdpr-vimeo-button {position:absolute; z-index:4; top:50%; left:50%; transform:translate(-50%,-50%); width:70px; height:70px; cursor:pointer; color:white;}\n\t\t\t.ma-gdpr-vimeo-button.button-vimeo {color:black; filter:drop-shadow(0px 0px 4px darkgray);}\n\t\t\t.ma-gdpr-vimeo-button.button-circle {filter:drop-shadow(0px 0px 4px darkgray);}\n\t\t\t.ma-gdpr-vimeo-button.button-circle-o {filter:drop-shadow(0px 0px 4px darkgray);}\n\t\t\t.ma-gdpr-vimeo-notice {position:absolute; z-index:2; 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;}\n\t\t\t.ma-gdpr-vimeo-notice:empty {display:none;}\n\t\t\t.ma-gdpr-vimeo-title {position:absolute; z-index:3; width:100%; top:1em; padding:0 1em; color:white; text-shadow: black 1px 1px 2px;}\n\t\t</style>\nEND_OF_STYLE;\n\t\tif ($this->footercode_minimize) { \n\t\t\t$style = preg_replace('/\\/\\*.*?\\*\\//','',$style); \n\t\t\t$style = preg_replace('/\\r?\\n */','',$style); \n\t\t\t$style = preg_replace('/\\t/','',$style); \n\t\t}\n\t\techo $style;\n\n\t\t// emit code\n\t\t// Vimeo Player API: https://developer.vimeo.com/player/sdk/embed\n\t\t$script = <<<'END_OF_SCRIPT'\n\t\t<script id=\"ma-gdpr-vimeo-script\" type=\"text/javascript\">\n\t\t\"use strict\";\n\n\t\t/* Check for Builder preview panes */\n\t\tif (\n\t\t\t(typeof window.parent?.bricksData?.wpEditor != 'undefined') /* Bricks*/\n\t\t|| \t(window.parent?.angular) /* Oxygen */\n\t\t) { \n\t\t\t/* Dummy Object w/o functionality for Bricks Builder */ \n\t\t\twindow.ma_gdpr_vimeo = {\n\t\t\t\tinit: function(){},\n\t\t\t\tclick: function($target){},\n\t\t\t}\n\t\t}\n\t\t\n\t\tif ((typeof window.ma_gdpr_vimeo == 'undefined')){\n\t\t\twindow.ma_gdpr_vimeo = {\n\t\t\t\tdebug: (new URLSearchParams(window.location.search)).get('ma-gdpr-vimeo-debug')!=null,\n\t\t\t\tplayer_observer_timer: 1000,\n\t\t\t\tplayer_observer_interval: null,\n\t\t\t\tplayers: {}, /* list of active players */\n\t\t\t\tlast_played: null,\n\n\t\t\t\tinit: function(){\n\t\t\t\t\tthis.debug && console.log('MA GDPR Vimeo initialized.');\n\t\t\t\t},\n\n\t\t\t\tplayer_observer_init: function(){\n\t\t\t\t\tif (this.player_observer_interval == null) {\n\t\t\t\t\t\tthis.player_observer_interval = setInterval(ma_gdpr_vimeo.player_observer, this.player_observer_timer);\n\t\t\t\t\t\tthis.debug && console.log('MA GDPR Vimeo Player Observer initialized.');\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tplayer_observer: function() {\n\t\t\t\t\t/* pauses video if iframe is not visible anymore - e.g. closed modal */\n\t\t\t\t\tma_gdpr_vimeo.debug && console.log('MA GDPR Vimeo Player Observer (entries: '+Object.values(ma_gdpr_vimeo.players).length+')');\n\t\t\t\t\tfor (let $playerID in ma_gdpr_vimeo.players) {\n\t\t\t\t\t\tlet $player = ma_gdpr_vimeo.players[$playerID];\n\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('MA GDPR Vimeo Player Observer checking player '+$playerID+'...');\n\t\t\t\t\t\t\n\t\t\t\t\t\t$player.player.getPaused().then(function ($paused) {\n\t\t\t\t\t\t\tlet $state = $paused ? 'STOP' : 'PLAY';\n\t\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('MA GDPR Vimeo Player Observer got status of video '+$playerID+' as '+$state); \n\t\t\t\t\t\t\tif ($state=='PLAY') {\n\t\t\t\t\t\t\t\tma_gdpr_vimeo.last_played = $playerID;\n\t\t\t\t\t\t\t\tlet $iframe = document.getElementById($playerID);\n\t\t\t\t\t\t\t\tif ($iframe) {\n\t\t\t\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('MA GDPR Vimeo Player Observer checking iframe offsetParent '+$iframe.offsetParent);\n\t\t\t\t\t\t\t\t\tif (($iframe.offsetParent === null)) {\n\t\t\t\t\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('MA GDPR Vimeo Player Observer pauses hidden video '+$playerID);\n\t\t\t\t\t\t\t\t\t\t$player.player.pauseVideo();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tplayer_stop_all_except: function($apiID) {\n\t\t\t\t\t/* loop through players and pause all except this one */\n\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Looping players', ma_gdpr_vimeo.players);\n\t\t\t\t\tfor (let $playerID in ma_gdpr_vimeo.players) {\n\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Checking player '+$playerID);\n\t\t\t\t\t\tif ($playerID != $apiID) {\n\t\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Pausing player '+$playerID);\n\t\t\t\t\t\t\tma_gdpr_vimeo.players[$playerID].player.pause().then(function() {\n\t\t\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Paused player '+$playerID);\n\t\t\t\t\t\t\t}).catch(function($error) {\n\t\t\t\t\t\t\t\tconsole.log('error: '+$error.name);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tclick: function($target){\n\t\t\t\t\tlet $wrapper = $target.closest('.ma-gdpr-vimeo-wrapper'); \n\t\t\t\t\tif (!$wrapper) return;\n\t\t\t\t\tif ($wrapper.getAttribute('data-new-window') == '1') {\n\t\t\t\t\t\t/* get the video id from the wrapper */\n\t\t\t\t\t\tlet $video_id = $wrapper.getAttribute('data-video-id');\n\t\t\t\t\t\twindow.open('https://vimeo.com/'+$video_id);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t/* initialize the player observer */\n\t\t\t\t\tma_gdpr_vimeo.player_observer_init();\n\n\t\t\t\t\t/* replace wrapper content with video iframe */\n\t\t\t\t\tlet b64 = $wrapper.querySelector('.ma-gdpr-vimeo-content').innerText;\n\t\t\t\t\tlet content = decodeURIComponent(atob(b64).split('').map(function(c) {\n\t\t\t\t\t\treturn '%'+('00'+c.charCodeAt(0).toString(16)).slice(-2);\n\t\t\t\t\t}).join(''));\n\t\t\t\t\t$wrapper.innerHTML = content;\n\n\t\t\t\t\t/* check if Vimeo API has already been loaded */\n\t\t\t\t\tif (document.querySelectorAll('#ma-gdpr-vimeo-player-api').length==0 ) {\n\t\t\t\t\t\t/* load the Vimeo API */\n\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Loading Vimeo API...');\n\t\t\t\t\t\tlet $script = document.createElement('script');\n\t\t\t\t\t\t$script.id = 'ma-gdpr-vimeo-player-api';\n\t\t\t\t\t\t$script.src = 'https://player.vimeo.com/api/player.js';\n\t\t\t\t\t\t/* handler for Vimeo API loaded */\n\t\t\t\t\t\t$script.onload = function ($script) {\n\t\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Vimeo API loaded.');\n\t\t\t\t\t\t\tma_gdpr_vimeo.video_start($wrapper);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tdocument.body.appendChild($script);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t/* Vimeo API is already loaded */\n\t\t\t\t\t\tma_gdpr_vimeo.video_start($wrapper);\n\t\t\t\t\t}\n\n\t\t\t\t},\n\n\t\t\t\tvideo_start: function($wrapper) {\n\t\t\t\t\t/* get the video id from the parent div's id attribute */\n\t\t\t\t\tlet $videoID = $wrapper.getAttribute('data-video-id');\n\t\t\t\t\tlet $playerID = $wrapper.getAttribute('id');\n\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Starting video '+$videoID+' from wrapper '+$playerID);\n\t\t\t\t\t/* get the inner dimensions of the wrapper */\n\t\t\t\t\tconst $wrapperWidth = window.getComputedStyle($wrapper).width;\n\t\t\t\t\tconst $wrapperHeight = window.getComputedStyle($wrapper).height;\n\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Video WxH',[$wrapperWidth,$wrapperHeight]);\n\n\t\t\t\t\t/* remove styles from wrapper */\n\t\t\t\t\t$wrapper.style.height = $wrapperHeight;\n\t\t\t\t\t$wrapper.style.padding = 'unset';\n\n\t\t\t\t\t/* connect player */\n\t\t\t\t\tlet $iframe = document.querySelector('#'+$playerID+' iframe');\n\t\t\t\t\tlet $player = new Vimeo.Player($iframe);\n\t\t\t\t\tma_gdpr_vimeo.players[$playerID] = {player:$player};\n\n\t\t\t\t\t/* handler to stop other videos */\n\t\t\t\t\t$player.on('play', function($data) {\n\t\t\t\t\t\tlet $playerID = this.element.offsetParent.getAttribute('id');\n\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Event play', this, $playerID, $data);\n\t\t\t\t\t\tma_gdpr_vimeo.player_stop_all_except($playerID);\n\t\t\t\t\t});\n\t\t\t\t\t/* now start video */\n\t\t\t\t\t$player.play();\n\t\t\t\t\tma_gdpr_vimeo.last_played = $playerID;\n\t\t\t\t},\n\t\t\t};\n\t\t\tma_gdpr_vimeo.init();\n\t\t}\n\n\t\t/* Accessibility: Handle Space or Enter as play click */\n\t\tdocument.querySelectorAll('.ma-gdpr-vimeo-button').forEach( ($elm) => {\n\t\t\t$elm.addEventListener('keyup', function($event) {\n\t\t\t\tif ($event.key==='Enter') {\n\t\t\t\t\t$event.preventDefault();\n\t\t\t\t\t$event.stopPropagation();\n\t\t\t\t\t$event.target.parentNode.querySelector('.ma-gdpr-vimeo-thumbnail').click();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\t/* Accessibility: Handle Space or Enter on BODY for playing or stopped video */\n\t\tdocument.addEventListener('keyup', function($event) {\n\t\t\tif (($event.key==='Enter') && ($event.target?.tagName==='BODY')) {\n\t\t\t\tma_gdpr_vimeo.debug && console.log('Last played: '+ma_gdpr_vimeo.last_played);\n\t\t\t\tif (!ma_gdpr_vimeo.last_played) return;\n\t\t\t\t$event.preventDefault();\n\t\t\t\t$event.stopPropagation();\n\t\t\t\tma_gdpr_vimeo.players[ma_gdpr_vimeo.last_played].player.getPaused().then(function($paused){\n\t\t\t\t\tif ($paused) {\n\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Start playing '+ma_gdpr_vimeo.last_played);\n\t\t\t\t\t\tlet $player = ma_gdpr_vimeo.players[ma_gdpr_vimeo.last_played];\n\t\t\t\t\t\t$player && $player.player && (typeof $player.player.play!=='undefined') && $player.player.play();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tma_gdpr_vimeo.debug && console.log('Stopping all');\n\t\t\t\t\t\tma_gdpr_vimeo.player_stop_all_except('');\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\t</script>\nEND_OF_SCRIPT;\n\t\tif ($this->footercode_minimize) { \n\t\t\t$script = preg_replace('/\\/\\*.*?\\*\\//','',$script); \n\t\t\t$script = preg_replace('/\\r?\\n */','',$script); \n\t\t\t$script = preg_replace('/\\t/','',$script); \n\t\t}\n\t\techo $script;\n\t\t\n\n\t\t// emit play button svg symbol\n\t\t$symbol = <<<'END_OF_SYMBOL'\n\t\t<svg id=\"ma-gdpr-vimeo-symbols\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" \n\t\t\t\t\taria-hidden=\"true\" style=\"position: absolute; width: 0; height: 0; overflow: hidden;\">\n\t\t\t<defs>\n\t\t\t\t<symbol id=\"ma-gdpr-vimeo-play-button-vimeo\" viewBox=\"0 0 500 350\" >\n\t\t\t\t\t<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\"/>\n\t\t\t\t\t<path fill=\"white\" d=\"M199.928,71.057l0.074,188.537l142.98-94.182 L199.928,71.057z\"/>\n\t\t\t\t</symbol>\n\t\t\t\t<symbol id=\"ma-gdpr-vimeo-play-button-circle\" viewBox=\"0 0 24 28\" >\n\t\t\t\t\t<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\"/>\n\t\t\t\t</symbol>\n\t\t\t\t<symbol id=\"ma-gdpr-vimeo-play-button-circle-o\" viewBox=\"0 0 24 28\" >\n\t\t\t\t\t<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\"/>\n\t\t\t\t</symbol>\n\t\t\t\t<symbol id=\"ma-gdpr-vimeo-play-button-play\" viewBox=\"0 0 24 28\" >\n\t\t\t\t\t<path fill=\"currentColor\" d=\"M21.625 14.484l-20.75 11.531c-0.484 0.266-0.875 0.031-0.875-0.516v-23c0-0.547 0.391-0.781 0.875-0.516l20.75 11.531c0.484 0.266 0.484 0.703 0 0.969z\"/>\n\t\t\t\t</symbol>\n\t\t\t</defs>\n\t\t</svg>\nEND_OF_SYMBOL;\n\t\tif ($this->footercode_minimize) { $symbol = preg_replace('/\\r?\\n[\\t ]*/','',$symbol); }\n\t\techo $symbol;\n\n\t\tDONE:\n\t\t$et = microtime(true);\n\t\tif (WP_DEBUG && $this->timing>1) {error_log(sprintf('%s() Timing: %.5f sec.', __METHOD__, $et-$st));}\n\t\t$this->timing_total_runtime += $et-$st;\n\t}\n\n\t//===================================================================================================================\n\t// UTILS\n\t//===================================================================================================================\n\t//-------------------------------------------------------------------------------------------------------------------\n\t/**\n\t * Returns the script details as type, version, combined.\n\t * @return object\tThe script  type and version:\n\t * - `type`: The type as 'Plugin' or 'Code Snippet'.\n\t * - `version`: The version.\n\t * - `combined`: The combination of type and version.\n\t * - `full`: The combination of type, title and version\n\t */\n\tpublic function get_script_details() {\n\t\t$type = basename(__FILE__) == 'ma-gdpr-vimeo.php' ? 'Plugin' : 'Code Snippet';\n\t\t$retval =(object)[\n\t\t\t'type'\t\t=> $type,\n\t\t\t'version'\t=> self::VERSION,\n\t\t\t'combined'\t=> sprintf('%s %s', $type, self::VERSION),\n\t\t\t'full'\t\t=> sprintf('%s \"%s\" %s', $type, self::TITLE, self::VERSION),\n\t\t];\n\t\treturn $retval;;\n\t}\n\n\n}\n\n\n//===================================================================================================================\n// Initialize\n$GLOBALS['MA_GDPR_Vimeo'] = new MA_GDPR_Vimeo();\n\t\nendif;\n",
      "active": true,
      "modified": "2025-07-11 20:52:53",
      "revision": "10"
    }
  ]
}