Code Snippet: GDPR compliant Vimeo Videos

Introduction
Vimeo videos are embedded on many websites.
If you use the standard embedding methods provided by WordPress (Classic Editor, Gutenberg) or page builders (Oxygen, and many others) for this purpose and don't ask for the visitor's consent beforehand, you'll probably violate the General Data Protection Regulation (GDPR) in the EU: When displaying a page that contains a Vimeo video, a connection to the Vimeo servers is automatically established in order to load the preview image. In the process, the visitor's IP address, and thus personal information, is transmitted to Vimeo. That's not allowed without explicit consent.
So how can you embed Vimeo videos on your website in a GDPR compliant way?
Solution
The code snippet described here provides a WordPress shortcode that enables a GDPR compliant and at the same time as simple and efficient as possible embedding of Vimeo videos.
Using this shortcode, the preview image for the video is already retrieved from the Vimeo server and cached locally. The visitor's browser is given this local preview image, along with a note about the privacy policy. The browser does not need to establish a connection to Vimeo at this point. The actual video is only loaded from Vimeo once the visitor has clicked on it. This procedure is GDPR compliant because the visitor must first actively confirm that he wants to load the video from Vimeo.
The code snippet also uses a "Do Not Track" parameter (dnt=1) to retrieve the video, thus avoiding the use of cookies on the part of Vimeo.
And by the way, this solution should also have a significant positive impact on the well-known speed test tools (Page Speed Insights, GTMetrix, …), since no data is loaded from external servers until the visitor has consented to this.
Shortcode
The simplest and shortest syntax for the shortcode is:
[ma-gdpr-vimeo video="814361316"]
where 814361316
is the ID of the Vimeo video. You can easily copy the ID from the URL of the Vimeo video:

This shortcode can easily be written directly in the Classic or Gutenberg editor.
In page builders, like e.g. Oxygen, there is usually a separate "Shortcode" element available for this purpose.
This shortcode generates the following output:

The thumbnail is not loaded from Vimeo, but from the local cache.
In the center, the thumbnail is overlaid by the play button similar to the one on Vimeo.
And at the bottom, the visitor sees a note on privacy policy in a colored bar.
Thumbnail
The snippet automatically loads the available thumbnails for this video from Vimeo and offers them to the browser.
Usually these are JPG files with a width of 100, 200, 295 and 640 pixels.
The browser selects the most suitable preview image for the respective screen resolution.
Note:
- The snippet provides the browser with a choice of all image sizes. The browser decides itself which image size to load.
Cache
The thumbnails loaded from Vimeo are automatically stored on the server in the /wp-content/uploads/ma-gdpr-vimeo/
directory. The directory is created automatically if it doesn't already exist. A separate subdirectory is created for each video ID.
Shortcode Parameters
For the shortcode to work, it is mandatory to specify the desired Vimeo video with the shortcode parameter video
.
In addition, the shortcode allows some more parameters, which are explained further.
video
The video
parameter is used to specify a Vimeo ID or a Vimeo URL for the desired video.
The Video ID consists of numbers and letters.
If a URL is specified, the ID is automatically determined from it. Numerous variants of Vimeo URLs are supported.
Samples:

[ma-gdpr-vimeo video="814361316"]

[ma-gdpr-vimeo video="https://vimeo.com/814361316"]
aspect-ratio
The default aspect ratio for Vimeo videos is 16:9.
The aspect-ratio
parameter allows you to use a different aspect ratio, such as 4:3 or 1:1 (square).
Any aspect ratio can be specified here as long as the syntax is kept as two numbers separated by a colon or a slash: "width:height" or "width/height"
The thumbnail and the Vimeo Player will automatically fit the aspect ratio specified here. The video itself will be shown in the original aspect ratio, possibly then with black bars.
Example:
[ma-gdpr-vimeo video="814361316" width="350px" aspect-ratio="1:1"]

gdpr-text
The code snippet defines a notice text about the privacy policy in different languages:
Language | Text |
---|---|
DA | Når du har trykket, vil videoen blive indlæst fra Vimeo's servere. Se vores privatlivspolitik for flere informationer. |
DE | Bei Klick wird dieses Video von den Vimeo Servern geladen. Details siehe Datenschutzerklärung. |
EN | When clicked, this video is loaded from Vimeo servers. See our privacy policy for details. |
ES | Al hacer clic, este vídeo se carga desde los servidores de Vimeo. Consulte la política de privacidad para más detalles. |
FI | Klikattuasi, tämä video ladataan Vimeon palvelimilta. Katso lisätietoja meidän tietosuojaselosteesta. |
FR | En cliquant, cette vidéo est chargée depuis les serveurs de Vimeo. Voir la politique de confidentialité. |
HU | Kattintás után ez a videó a Vimeo szervereiről kerül lejátszásra. A részletekért olvassa el az Adatkezelési Tájékoztatót oldalt. |
IT | Quando si clicca, questo video viene caricato dai server di Vimeo. Vedere l'informativa sulla privacy per i dettagli. |
JA | クリックすると、この動画が Vimeo サーバーから読み込まれます。詳細については、プライバシー ポリシー をご覧ください。 |
The language is selected based on your website's or page language. Polylang language support is included in the snippet.
If no default text is available for your page's language, English is selected instead.
The notice text can be customized to your own requirements, for example other languages, via parameter gdpr-text
, and can contain the following placeholders:{privacy-policy-url}
will be replaced by the URL of the privacy policy page configured in WordPress.{privacy-policy-link}
will be replaced with a full link to the privacy policy page configured in WordPress.
Note: The predefined default texts include a link to the privacy policy, if it's properly configured in WordPress.
Example:
[ma-gdpr-vimeo video="814361316" gdpr-text="Wanneer erop wordt geklikt, wordt deze video van de Vimeo-servers geladen. Zie het {privacy-policy-link} voor details."]

Note: In this case the page title of the privacy policy is displayed in English, since this is the default language for this page.
The parameter gdpr-text
can also be set to be empty. In this case, no hint text is displayed at all. This is especially conceivable in conjunction with the parameter new-window
.
[ma-gdpr-vimeo video="814361316" gdpr-text=""]

gdpr-text-size
By default, the notice text is displayed in the font size 0.7em, i.e. 70% of the text size defined for this block.
The gdpr-text-size
parameter can be used to adjust the text size to your own requirements. Any valid CSS specification for the text size is allowed.
Example:
[ma-gdpr-vimeo video="814361316" gdpr-text-size="20px"]

notice-style
The design of the privacy notice can be customized as needed.
White font on red bar? No problem!
Example:
[ma-gdpr-vimeo video="814361316" notice-style="background-color:red; color:white"]

notice-class
With an own CSS class this can be done even more targeted. For example, you can also control the design of the link:
Example:
<style>
body .my-notice-class {
background-color: red;
color:white;
}
body .my-notice-class a {
color: white;
font-weight: bold;
}
</style>
[ma-gdpr-vimeo video="814361316" notice-class="my-notice-class"]

width
By default, a width of 100% is set for the video block. The video block thus occupies the entire width of the enclosing block, e.g. a DIV or a column. The default of 100% allows easy adjustment of the responsive view by the parent element.
The height is automatically calculated from the width and aspect ratio.
If needed, the width can be changed with the width
parameter. All valid CSS specifications for the width of an element are allowed.
Example:
[ma-gdpr-vimeo video="814361316" width="300px"]

Note: I recommend to leave the width at the default of 100% and instead style the enclosing block for responsive viewports using CSS media queries.
alt
/ title
The alt
and title
parameters set the corresponding HTML attributes for the preview image. The alt
attribute is used to support search engines and accessibility, the title
attribute provides the tooltip text.
[ma-gdpr-vimeo video="814361316" alt="The earth rotates" title="Earth rotation"]

Vimeo metadata can also be displayed here.
thumbnail
With the parameter thumbnail
you can use your own thumbnail instead of the one loaded from Vimeo.
Either a full URL to an image can be specified here, or the media ID of an image that has already been uploaded to WordPress.

[ma-gdpr-vimeo video="814361316" thumbnail="https://www.altmann.de/wp-content/uploads/gdpr-vimeo-nasa.jpg"]

[ma-gdpr-vimeo video="814361316" thumbnail="2361"]
title-text
The snippet shows the preview image from Vimeo, but no title to the video.
The parameter title-text
can be used to display a title above the preview image.
[ma-gdpr-vimeo video="814361316" title-text="Look how the earth is spinning...."]

The title is displayed by default in the top left corner, white font in standard size, with a subtle shadow to make the text readable even on bright thumbnails.
Vimeo metadata can also be displayed here.
title-style
The default style for the title is defined as:
.ma-gdpr-vimeo-title {
position:absolute;
width: 100%;
top: 1em;
padding: 0 1em;
color: white;
text-shadow: black 1px 1px 2px;
}
The parameter title-style
can be used to adjust the positioning and design of the title.
Positioning

[ma-gdpr-vimeo video="814361316"title-text="Once around the world..." title-style="top:unset; bottom:30%;text-align:center"]
Design

[ma-gdpr-vimeo video="814361316" title-text="Once around the world..." title-style="top:1rem; font-size:2em; color:red; text-align:center;"]
title-class
To globally control the positioning and design of the titles, the parameter title-class
allows to specify a global CSS class.
Beispiel:
<style>
body .my-video-title-class {
top: 1rem;
font-size: 1.5em;
line-height: 1;
color: blue;
text-align: center;
}
body .my-video-title-class span {
display: inline-block;
transform: rotate(-15deg);
color:red;
}
</style>
[ma-gdpr-vimeo video="814361316" title-text="Planet Earth <span>with Clouds</span>" title-class="my-video-title-class"]

play-button
As of version 1.2.0 the snippet provides different play button types.
The default button type is vimeo
, which does not need to be specified explicitly.
The parameter play-button
can be used to select the desired button:
Button Type vimeo

[ma-gdpr-vimeo video="814361316" play-button="vimeo"]
Button Typ circle

[ma-gdpr-vimeo video="814361316" play-button="circle"]
Button Typ circle-o

[ma-gdpr-vimeo video="814361316" play-button="circle-o"]
Button Typ play

[ma-gdpr-vimeo video="814361316" play-button="play"]
play-button
-color
The color of the Vimeo Play button is black. The other button types have white
as default color.
The parameter play-button-color
can be used to set a different color for the button.

[ma-gdpr-vimeo video="814361316" play-button-color="blue"]

[ma-gdpr-vimeo video="814361316" play-button="circle" play-button-color="green"]

[ma-gdpr-vimeo video="814361316" play-button="circle-o" play-button-color="black"]
play-button-class
The parameter play-button-class
allows you to use a custom CSS class for the play button.
A green play button on the "Green Energy" page (Post ID 2370)? No problem!
Beispiel:
<style>
body.postid-2370 .my-play-button {
color:green;
}
</style>
[ma-gdpr-vimeo video="814361316" play-button-class="my-play-button"]

new-window
The parameter new-window
opens the video in a new browser tab directly on the Vimeo page.
This may be desirable if you want to display only very small thumbnails of the videos in WordPress, which are unsuitable for direct integration of the player.
[ma-gdpr-vimeo video="814361316" new-window=1]

Global Parameters
All shortcode parameters can be defined globally.
This simplifies the uniform design of the videos and spares the definition of the parameters for each individual video.
For this purpose, a global variable $GLOBALS['ma_gdpr_vimeo']
is created as an array and supplied with the desired settings.
This can be done quite easily with another code snippet with e.g. the following content:
$GLOBALS['ma_gdpr_vimeo'] = [
'play-button' => 'play',
'play-button-class' => 'my-play-button',
];
A special solution applies here to the gdpr-text
parameter, which is language-sensitive.
The specification of a global parameter gdpr-text
sets the text globally to a uniform value.
The addition of a language flag controls the text output per language.
$GLOBALS['ma_gdpr_vimeo'] = [
'gdpr-text' => 'Read the {privacy-policy-link}.',
'gdpr-text-de' => 'Info zum <a href="{privacy-policy-url}" target="_blank">Datenschutz</a>',
];
More Vimeo Parameters
Vimeo itself offers even more parameters for controlling video playback and the appearance of the player.
An overview of the available parameters can be found here: https://developer.vimeo.com/api/oembed/videos.
Currently, these parameters are not supported, but may be implemented upon request and demand.
Vimeo Player
Clicking on a video activates an iFrame that loads the video from the Vimeo servers.
All of Vimeo's usual features are available here.
The player automatically pauses an already running video when another video is started. So two videos are not running at the same time.
Vimeo Metadata
The snippet automatically loads various metadata about the embedded video from Vimeo.
This includes the video title, author and description.
This metadata can be used via placeholders to attribute the video.
Currently supported are the placeholders @title@
, @author_name@
, @description@
in the alt
, title
and title-text
parameters.
Sample:
[ma-gdpr-vimeo video="814361316" title="@title@" title-text="@title@ by @author_name@<br>@description@" alt="@title@"]

Created with Fusion, 2017
FAQ – Frequently Asked Questions
Download
The code snippet is available for download here:
ma-gdpr-vimeo.code-snippets.json
Version 1.1.0, 2023-04-04
For installation and use of the downloaded JSON file you will need the plugin Code Snippets or Advanced Scripts.
You can install the JSON file using the "Import" function of the plugin.
Don't forget to activate the snippet after import.
Alternative: At the end of this page you can view and copy the complete source code of the snippet.
New functionalities and bug fixes are documented in the change log.
Donation
I enjoy developing code snippets and solving requirements with them. I provide the snippets free of charge.
If you like, you can honor my many hours of work with a small coffee donation via PayPal.
Your donation will of course be taxed properly by me.
Disclaimer
I have developed and tested the code snippet to the best of my knowledge under
- WordPress 6.1.1, 6.2
- Oxygen 4.5
- Bricks 1.7.1
- PHP 8.1
I provide the code snippet for free use.
I cannot give any guarantee for the functionality because of the countless possible variations in WordPress environments.
Download and use of this code snippet is at your own risk and responsibility.
Change Log
See "Version History" in Source Code
Source Code
<?php /* Plugin Name: MA GDPR Vimeo Description: GDPR compliant Vimeo video embedding Author: <a href="https://www.altmann.de/">Matthias Altmann</a> Project: Code Snippet: GDPR Compliant Vimeo Embed Version: 1.1.0 Plugin URI: https://www.altmann.de/blog/code-snippet-gdpr-compliant-vimeo-videos/ Description: en: https://www.altmann.de/blog/code-snippet-gdpr-compliant-vimeo-videos/ de: https://www.altmann.de/blog/code-snippet-dsgvo-konforme-vimeo-videos/ Copyright: © 2023, Matthias Altmann Version History: Date Version Description -------------------------------------------------------------------------------------------------------------- 2023-04-04 1.1.0 New Feature: Pause handler for closed Oxygen Modals 2023-04-03 1.0.0 Initial Release 2023-04-02 0.0.0 Development start Samples https://vimeo.com/347119375 > Sample Video https://vimeo.com/814361316 > Orbiting Earth Documentation: https://developer.vimeo.com/api/oembed/videos */ if (!class_exists('MA_GDPR_Vimeo')) : class MA_GDPR_Vimeo { const TITLE = 'MA GDPR Vimeo'; const SLUG = 'ma-gdpr-vimeo'; const VERSION = '1.1.0'; // ===== CONFIGURATION ============================================================================================== public static $timing = false; // Write timing info to wordpress debug.log if WP_DEBUG enabled private static $default_width = '100%'; // width of the video block. Can be specified in %, px private static $default_aspect_ratio = '16/9'; // aspect ratio of the video block. Syntax X:X or X/X private static $default_gdpr_text_size = '.7em'; // font size for GDPR text private static $default_new_window = false; // open video in new window private static $default_gdpr_text = [ // GDPR notice text in different languages. 'da' => ['Når du har trykket, vil videoen blive indlæst fra Vimeo\'s servere. Se vores %s for flere informationer.','privatlivspolitik'], 'de' => ['Bei Klick wird dieses Video von den Vimeo Servern geladen. Details siehe %s.', 'Datenschutzerklärung'], 'en' => ['When clicked, this video is loaded from Vimeo servers. See our %s for details.', 'privacy policy'], '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'], 'fi' => ['Klikattuasi, tämä video ladataan Vimeon palvelimilta. Katso lisätietoja meidän %s.', 'tietosuojaselosteesta'], 'fr' => ['En cliquant, cette vidéo est chargée depuis les serveurs de Vimeo. Voir la %s.', 'politique de confidentialité'], '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'], 'it' => ['Quando si clicca, questo video viene caricato dai server di Vimeo. Vedere %s per i dettagli.', 'l\'informativa sulla privacy'], 'ja' => ['クリックすると、この動画が Vimeo サーバーから読み込まれます。詳細については、%s をご覧ください。', 'プライバシー ポリシー'], ]; // ===== INTERNAL =================================================================================================== private static $trace_level_base = 0; // initial trace 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 $total_runtime = 0; private static $urlformat_json_embed= 'https://vimeo.com/api/oembed.json?url=https%%3A//vimeo.com/%s'; private static $urlformat_json_api = 'http://vimeo.com/api/v2/video/%s.json'; //------------------------------------------------------------------------------------------------------------------- /** * Initialize the snippet * */ public static function init() { $st = microtime(true); if (WP_DEBUG && self::$timing) {self::$trace_level_base = count(debug_backtrace()) -1;} // check thumbnails directory 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-vimeo', [__CLASS__, 'shortcode']); add_action('wp_footer',[__CLASS__,'footercode']); // for timing add_action('shutdown', [__CLASS__,'total_runtime']); if ( ($_GET['ct_builder']??null) == 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));} self::$total_runtime += $et-$st; } //------------------------------------------------------------------------------------------------------------------- /** * Return padding for 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(!DEBUG_BACKTRACE_PROVIDE_OBJECT|DEBUG_BACKTRACE_IGNORE_ARGS)) - self::$trace_level_base); } //------------------------------------------------------------------------------------------------------------------- /** * Logs total timing for shortcodes on a page */ public static function total_runtime(){ if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s() Timing: %.5f sec.', self::get_callstack_padding(), __CLASS__, __FUNCTION__,self::$total_runtime));} } //------------------------------------------------------------------------------------------------------------------- /** * 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() use($type, $msg){ echo sprintf('<div class="notice notice-%s"><p>[%s] %s</p></div>', $type, MA_GDPR_Vimeo::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-vimeo/ if necessary * @return object|null 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'].'/'.self::SLUG; $retval->url = $thumbnail_dir_info['baseurl'].'/'.self::SLUG; // 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(string $text='privacy policy') { $pplink = get_the_privacy_policy_link(); return $pplink ? $pplink : $text; } //------------------------------------------------------------------------------------------------------------------- /** * Parses a Vimeo URL for a video ID. * Handles numerous URL formats, or plain video ID. * @param string $s The URL/string to parse * @return array Array containing key v (video ID), or empty */ private static function parse_video_url(string $s=''): array { $st = microtime(true); $retval = []; // regex for parsing vimeo url variants $re = '/^ (?:https?\:)?\/{2} # protocol http, https, or schemeless (?:www\.)? # optional www (?:(?:player\.)?vimeo\.com)\/ # domain variants (?:(?:video)[\/]?) ? # video term ([A-Za-z0-9\-\_]+) # THE ID /x'; // parse url if (preg_match($re,$s,$matches)) {$retval['v'] = $matches[1];} // id only? else if (preg_match('/^([A-Za-z0-9\-\_]+)$/',$s,$matches)) {$retval['v'] = $matches[1];} $et = microtime(true); if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s("%s") => %s Timing: %.5f sec.', self::get_callstack_padding(), __CLASS__, __FUNCTION__, $s, json_encode($retval), $et-$st));} return $retval; } //------------------------------------------------------------------------------------------------------------------- /** * Get the image type as extension string from an integer value. Handles gif, jpg, pmg, webp. * @param int $image_type The image type as int * @return string The image extension */ private static function get_image_type_as_string(int $image_type): string { switch ($image_type) { case IMAGETYPE_GIF: return 'gif'; case IMAGETYPE_JPEG: return 'jpg'; case IMAGETYPE_PNG: return 'png'; case IMAGETYPE_WEBP: return 'webp'; default: return ''; } } //------------------------------------------------------------------------------------------------------------------- /** * Get type, width, height from an image file. * @param string $filepath The image file path * @return object The image info object: (bool) status, (string) type, (int) width, (int) height */ private static function get_image_info(string $filepath): object { $retval = (object)[ 'status' => false, 'type' => null, 'width' => null, 'height' => null, ]; if ($result = getimagesize($filepath)) { $retval->status = true; $retval->type = self::get_image_type_as_string($result[2]); $retval->width = $result[0]; $retval->height = $result[1]; } return $retval; } //------------------------------------------------------------------------------------------------------------------- /** * Parses some info from an vimeo embed request result * @param object $data The embed request result * @return object The info parsed */ private static function parse_embed_info(object $data): object { $retval = (object)[ 'embed_code' => null, 'app_id' => null, 'title' => null, 'status_code' => null, ]; $html = $data->html??''; /* Sample: <iframe src="https://player.vimeo.com/video/347119375?h=1699409fe2&app_id=122963&autoplay=1&color=ef2200&byline=0&portrait=0" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen title="Sample Video"></iframe> */ if (preg_match('/[\?&;]h=([\da-f]+)/',$html,$matches)) {$retval->embed_code = $matches[1];} if (preg_match('/[\?&;]app_id=([\da-f]+)/',$html,$matches)) {$retval->app_id = $matches[1];} if (preg_match('/title="([^"]+)"/',$html,$matches)) {$retval->title = html_entity_decode($matches[1]);} if ($data->domain_status_code??'') {$retval->domain_status_code = $data->domain_status_code;} return $retval; } //------------------------------------------------------------------------------------------------------------------- /** * Get details for video $id, containing e.g. thumbnails, if already downloaded. Load if not yet available. * @param string $id The video ID * @return object Object with (bool) status, (int) status_code, (string) status_text, (object) info, (object) thumbnails */ private static function get_video_details(string $id): object { $st = microtime(true); $retval = (object)[ 'status' => false, 'notes' => [], 'embed' => (object)['code'=>null,'text'=>null,'data'=> null], 'api' => (object)['code'=>null,'text'=>null,'data'=> null], 'thumbnails' => null, ]; 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)) { $note = sprintf('Error creating cache folder for video %s.', $id); $retval->notes[] = $note; error_log('['.self::TITLE.'] '.$note); goto DONE; } } $info_file_path = $vid_dir.'/'.$id.'.json'; // read from cache if available if (file_exists($info_file_path)) { // read from cache $retval = json_decode(@file_get_contents($info_file_path)); // status OK and thumbnails available? if ($retval->status && ($retval->thumbnails??[])) { goto DONE; } } // reset notes from previous attempts $retval->notes = []; // Retrieve from Vimeo, using current language $referer = ($_SERVER['HTTPS']??'' == 'on' ? 'https:' : 'http').'//'.($_SERVER['HTTP_HOST']??''); $http_request_headers = [ 'Accept-language: '.self::get_current_language(), 'Referer: '.$referer, ]; $options = ['http'=>['method'=>'GET','header'=>implode("\r\n",$http_request_headers)]]; $context = stream_context_create($options); // get embed info, to get the embed code $url = sprintf(self::$urlformat_json_embed, $id); $result = @file_get_contents($url, false, $context); // check status $http_status = $http_response_header[0]; preg_match( "#HTTP/[0-9\.]+\s+([0-9]+)\s(.*)#",$http_status, $matches); $retval->embed->code = intval($matches[1]); $retval->embed->text = trim($matches[2]); if ($retval->embed->code == 200) { $retval->embed->data = json_decode($result); // parse embed info $parsed = self::parse_embed_info($retval->embed->data); if (($parsed->domain_status_code??null) == 403) { $retval->embed->code = 403; $retval->embed->text = 'Forbidden'; } if (($parsed->domain_status_code??null) == 404) { $retval->embed->code = 404; $retval->embed->text = 'Not Found'; } $retval->parsed = $parsed; } if ($retval->embed->code != 200) { $note = sprintf('Video %s embed: %s', $id, $retval->embed->text); $retval->notes[] = $note; error_log('['.self::TITLE.'] '.$note); goto DONE; } // get api info $url = sprintf(self::$urlformat_json_api, $id); $result = @file_get_contents($url, false, $context); // check status $http_status = $http_response_header[0]; preg_match( "#HTTP/[0-9\.]+\s+([0-9]+)\s(.*)#",$http_status, $matches); $retval->api->code = intval($matches[1]); $retval->api->text = trim($matches[2]); if ($retval->api->code == 200) { $retval->api->data = json_decode($result); // flatten if json is an array if (is_array($retval->api->data)) {$retval->api->data = $retval->api->data[0];} } else { $note = sprintf('Video %s API: %s', $id, $retval->api->text); $retval->notes[] = $note; error_log('['.self::TITLE.'] '.$note); } $retval->status = (($retval->embed->code??null) == 200) && (($retval->api->code??null) == 200); // check thumbnails if ($retval->status && !($retval->thumbnails??null)) { $retval->thumbnails = (object)[]; $img_path = $vid_dir.'/'.$id; foreach(['embed:url','api:small','api:medium','api:large'] as $slug) { list($obj,$tag) = explode(':',$slug,2); if ($img_url = $retval->{$obj}->data->{'thumbnail_'.$tag}??null) { // load and store thumbnail if ($img_data = @file_get_contents($img_url)) { $status = $http_response_header[0]; if (!preg_match('/200\sOK/',$status)) continue; // store as tmp file to get image type from exif file_put_contents($img_path.'.tmp', $img_data); $img_info = self::get_image_info($img_path.'.tmp'); if ($img_info->status) { $img_spec = $img_info->width.'.'.$img_info->type; // store with correct filename if (@rename($img_path.'.tmp', $img_path.'_'.$img_spec)) { // update json $name = basename($img_path.'_'.$img_spec); $retval->thumbnails->{$img_info->width} = $name; @file_put_contents($info_file_path, json_encode($retval,JSON_PRETTY_PRINT)); } } } } } } DONE: // save video details, even if not yet complete @file_put_contents($info_file_path, json_encode($retval,JSON_PRETTY_PRINT)); $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(): string { $retval = get_locale(); // Is a translation plugin active? Supporting Polylang, WPML foreach (['pll_current_language','wpml_current_language'] as $func) { if (function_exists($func)) {$retval = $func(); break;} } $retval = str_replace('_','-',$retval); $retval = explode('-',$retval??'')[0]; return $retval; } //------------------------------------------------------------------------------------------------------------------- /** * Handle the shortcode "ma-gdpr-vimeo". * @param array $atts The shortcode attributes * @param string $content The content of the shortcode * @return string The output */ public static function shortcode(array $shortcode_atts = [], string $content = ''): string { $st = microtime(true); if (WP_DEBUG && self::$timing) {self::$trace_level_base = count(debug_backtrace()) -1;} $lang = self::get_current_language(); // get defaults for unspecified attributes $atts_default = [ 'slug' => self::SLUG, 'video' => 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' => null, 'title' => null, 'thumbnail' => null, 'title-text' => null, 'title-class' => null, 'title-style' => null, 'play-button' => 'vimeo', // currently included: vimeo, circle, circle-o, play 'play-button-style' => null, 'play-button-color' => null, 'new-window' => self::$default_new_window, ]; // merge global settings $atts = array_merge($atts_default, $GLOBALS['ma_gdpr_vimeo']??[]); // choose correct language for gdpr-text if ($GLOBALS['ma_gdpr_vimeo']['gdpr-text-'.strtolower($lang)]??'') { $atts['gdpr-text'] = $GLOBALS['ma_gdpr_vimeo']['gdpr-text-'.strtolower($lang)]; } // allow html in shortcode attributes title-text, gdpr-text foreach (['title-text','gdpr-text'] as $att) { if ($shortcode_atts[$att]??'') {$shortcode_atts[$att] = html_entity_decode($shortcode_atts[$att]);} } // merge shortcode attributes $atts = (object)array_merge($atts, $shortcode_atts); if ($atts->video) { $video = self::parse_video_url($atts->video); if (isset($video['v'])) { $atts->id = $video['v']; } } // any other parameter will be passed to vimeo directly $vimeo_parameters = []; foreach ($atts as $att_key => $att_val) { if (!in_array($att_key,array_keys($atts_default))) { $vimeo_parameters[$att_key] = $att_val; } } 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->slug.'-'.uniqid(); if (!self::$thumbnails_base) {return sprintf('[%s] Error creating thumbnail directory.',self::TITLE);} // check if we already have a thumbnail $video_details = self::get_video_details($atts->id); if (!$video_details->status) {return sprintf('[%s] %s',self::TITLE, implode(', ',$video_details->notes));} // insert vimeo attributes if ($video_details->status) { foreach (['alt','title','title-text'] as $attr) { foreach (['title','author_name','description'] as $info) { $atts->{$attr} = str_replace('@'.$info.'@', $video_details->embed->data->{$info}??'', $atts->{$attr}??''); } } } $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-vimeo-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??''); } } else { $sources = []; $thumbnail .= '<picture class="ma-gdpr-vimeo-thumbnail">'; $thumbnail .= sprintf('<img loading="lazy" src="%s" alt="%s" title="%s">',$atts->thumbnail, $atts->alt, $atts->title); $thumbnail .= '</picture>'; } } else { $thmbs = $video_details->thumbnails; foreach(get_object_vars($thmbs) as $size => $name) { $type = substr(strrchr($name,'.'),1); // to improve thumbnail quality, use higher res image if we reach half its size $sources[] = '<source media="(min-width:'.($size/2).'px)" type="image/'.$type.'" srcset="'.self::$thumbnails_base->url.'/'.$atts->id.'/'.$name.'">'; } // get smallest thumbnail as img src $thmbs = (array)$thmbs; $smallest = array_shift($thmbs); $img_src = self::$thumbnails_base->url.'/'.$atts->id.'/'.$smallest; $thumbnail .= '<picture id="'.$atts->uniqid.'-thumbnail'.'" class="ma-gdpr-vimeo-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('/',str_replace(':','/',$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-vimeo-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.'"';} // vimeo iframe $content = <<<END_OF_CONTENT <iframe src="https://player.vimeo.com/video/{$video_details->api->data->id}?h={$video_details->parsed->embed_code}&app_id={$video_details->parsed->app_id}&autoplay=1&dnt=1" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe> <script src="https://player.vimeo.com/api/player.js"></script> END_OF_CONTENT; $aspect_ratio = str_replace(':','/',$atts->{'aspect-ratio'}); $new_window = $atts->{'new-window'}; $vimeo_params = count($vimeo_parameters) ? base64_encode(json_encode($vimeo_parameters)) : ''; $play_button = $atts->{'play-button'} ?? ''; $play_button_class = $atts->{'play-button-class'} ?? ''; $notice_class = $atts->{'notice-class'} ?? ''; $notice_style = $atts->{'notice-style'} ?? ''; $gdpr_text_size = $atts->{'gdpr-text-size'}; $gdpr_text = $atts->{'gdpr-text'}; $content_b64 = base64_encode($content); $retval = <<<END_OF_HTML <div id="{$atts->uniqid}" data-video-id="{$atts->id}" class="ma-gdpr-vimeo-wrapper" style="width:{$block_width};aspect-ratio:{$aspect_ratio};" data-new-window="{$new_window}" data-parameters="{$vimeo_params}"> {$thumbnail} <svg id="{$atts->uniqid}-button" role="button" class="ma-gdpr-vimeo-button button-{$play_button} {$play_button_class}" {$play_button_style}><use xlink:href="#ma-gdpr-vimeo-play-button-{$play_button}"></use></svg> {$title_overlay} <div class="ma-gdpr-vimeo-notice {$notice_class}" style="font-size:{$gdpr_text_size}; {$notice_style}">{$gdpr_text}</div> <div id="{$atts->uniqid}-content" class="ma-gdpr-vimeo-content" style="display:none">{$content_b64}</div> </div> END_OF_HTML; self::$footercode_needed = true; 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));} self::$total_runtime += $et-$st; return $retval; } //------------------------------------------------------------------------------------------------------------------- /** * Emits style, script, svg definitions */ public static function footercode() { $st = microtime(true); if (!self::$footercode_needed) {goto DONE;} // emit style $style = <<<'END_OF_STYLE' <style id="ma-gdpr-vimeo-style"> .ma-gdpr-vimeo-wrapper {position:relative; display:flex; isolation:isolate;} .ma-gdpr-vimeo-thumbnail {position:absolute; z-index:1; top:0; left:0; width:100%; height:100%; display:flex; cursor:pointer; } .ma-gdpr-vimeo-thumbnail img {width:100%; height:100%; object-fit:cover; object-position:50% 50%;} .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;} .ma-gdpr-vimeo-button.button-vimeo {color:black; filter:drop-shadow(0px 0px 4px darkgray);} .ma-gdpr-vimeo-button.button-circle {filter:drop-shadow(0px 0px 4px darkgray);} .ma-gdpr-vimeo-button.button-circle-o {filter:drop-shadow(0px 0px 4px darkgray);} .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;} .ma-gdpr-vimeo-notice:empty {display:none;} .ma-gdpr-vimeo-title {position:absolute; z-index:3; 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 /** * Oxygen Modals, on close, reset src attribute of iframes to stop running videos. * Since our videos are configured to autoplay, they restart when a Modal is closed. * To avoid that we install an observer that pauses a video if it's not visible. */ $script = <<<'END_OF_SCRIPT' <script id="ma-gdpr-vimeo-script" type="text/javascript"> function ma_gdpr_vimeo_modal_observer(wrapper_id){ let iframe = document.querySelector('#'+wrapper_id+' iframe'); if (iframe && iframe.src.startsWith("https://player.vimeo.com/") && (iframe.offsetParent === null)) { iframe.contentWindow.postMessage('{"method":"pause"}', "*"); return; } setTimeout(ma_gdpr_vimeo_modal_observer, 1000, wrapper_id); } document.querySelectorAll('.ma-gdpr-vimeo-wrapper :is(img,.ma-gdpr-vimeo-button)').forEach( ($trigger) => { $trigger.addEventListener('click',function(){ let wrapper = this.closest('div.ma-gdpr-vimeo-wrapper'); if (wrapper.getAttribute('data-new-window') == '1') { /* get the video id from the wrapper */ let video_id = wrapper.getAttribute('data-video-id'); window.open('https://vimeo.com/'+video_id); return; } let b64 = wrapper.querySelector('.ma-gdpr-vimeo-content').innerText; let content = decodeURIComponent(atob(b64).split('').map(function(c) { return '%'+('00'+c.charCodeAt(0).toString(16)).slice(-2); }).join('')); wrapper.innerHTML = content; /* for video contained in an Oxygen Modal install the pause handler */ let modal = wrapper.closest('.ct-modal'); if (modal != null) {setTimeout(ma_gdpr_vimeo_modal_observer, 1000, wrapper.getAttribute('id'));} }); }); </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-vimeo-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-vimeo-play-button-vimeo" 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-vimeo-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-vimeo-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> <symbol id="ma-gdpr-vimeo-play-button-play" viewBox="0 0 24 28" > <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"/> </symbol> </defs> </svg> END_OF_SYMBOL; if (self::$footercode_minimize) { $symbol = preg_replace('/\r?\n[\t ]*/','',$symbol); } echo $symbol; // debugging info $codebase = basename(__FILE__) == 'ma-gdpr-vimeo.php' ? 'Plugin' : 'Code Snippet'; echo sprintf('<span data-nosnippet style="display:none">%s %s %s</span>', $codebase, self::SLUG, self::VERSION); 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));} self::$total_runtime += $et-$st; } } //=================================================================================================================== // Initialize // Warn about incompatibilities add_action('wp_loaded',function(){ if (is_admin()) { if (!isset($GLOBALS['MA_GDPR_Vimeo_Incompatibilities'])) {$GLOBALS['MA_GDPR_Vimeo_Incompatibilities'] = [];} $codebase = basename(__FILE__) == 'ma-gdpr-vimeo.php' ? 'Plugin' : 'Code Snippet'; // PHP allow_url_fopen = Off if (!ini_get('allow_url_fopen')) {$GLOBALS['MA_GDPR_VimeoIncompatibilities'][] = 'PHP setting <code>allow_url_fopen</code> needs to be <b><On</b> for the '.$codebase.' to work correctly.';} if (count($GLOBALS['MA_GDPR_Vimeo_Incompatibilities'])) { if (WP_DEBUG && MA_GDPR_Vimeo::$timing) {error_log('MA_GDPR_Vimeo / Incompatibilities: '.print_r($GLOBALS['MA_GDPR_Vimeo_Incompatibilities'],true));} add_action('admin_notices', function() use ($codebase){ if (WP_DEBUG ) {error_log('MA_GDPR_Vimeo/ Incompatibilities: '.print_r($GLOBALS['MA_GDPR_Vimeo_Incompatibilities'],true));} echo '<div class="notice notice-warning is-dismissible"> <p>The '.$codebase.' "'.MA_GDPR_Vimeo::TITLE.'" is skipped: '.implode(' or ',$GLOBALS['MA_GDPR_Vimeo_Incompatibilities']).'</p> </div>'; }); } } }, 1000); add_action('wp_loaded',function(){ if (count($GLOBALS['MA_GDPR_Vimeo_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 (is_favicon()) return; // don't run for favicon request if ((($_SERVER['QUERY_STRING']??null) == 'service-worker')) return; // don't run for service-worker if ((($_SERVER['REQUEST_URI']??null) == '/favicon.ico')) return; // don't run for favicon if ((strpos(($_SERVER['REQUEST_URI']??''),'/apple-touch-icon') === 0)) return; // don't run for apple touch icons if ((strpos(($_SERVER['REQUEST_URI']??''),'/wp-content/') === 0)) return; // don't run for dynamic wp-content file if (wp_is_json_request()) { // we need to handle some JSON requests to render shortcode in Oxygen Builder // check for Oxygen actions that might involve shortcodes if (!in_array(($_REQUEST['action']??null), ['ct_render_shortcode','ct_exec_code','oxy_render_easy_posts'])) { if (WP_DEBUG && MA_GDPR_Vimeo::$timing) {error_log(sprintf('%s skipping for JSON request: %s', MA_GDPR_Vimeo::TITLE, json_encode($_REQUEST)));} return; } } if (is_admin()) { global $pagenow; if ( ($pagenow != 'post-new.php') && ( ($pagenow != 'post.php') || ($pagenow == 'post.php' && (($_REQUEST['action']??null) != 'edit')) ) ) return; // only load on specific requests where Gutenberg is involved } $codebase = basename(__FILE__) == 'ma-gdpr-vimeo.php' ? 'Plugin' : 'Code Snippet'; if (WP_DEBUG && MA_GDPR_Vimeo::$timing) {error_log(sprintf('%s Initializing %s for %srequest URI="%s" action="%s"', MA_GDPR_Vimeo::TITLE, $codebase, wp_is_json_request()?'JSON ':'',$_SERVER['REQUEST_URI']??'', $_REQUEST['action']??''));} MA_GDPR_Vimeo::init(); }, 1200); endif;