Every filter and action Calucon Third-Party Embed Gate fires, with its signature, firing point, return contract, parameters and a runnable example. It also covers the settings schema, the WP-CLI commands, the placeholder markup contract, the data attributes, the CSS custom properties and the front-end JavaScript surface.
Version 1.0.0: 15 filters and 3 actions, documented from the plugin source. Requires WordPress 5.9 and PHP 7.4. No hook is required for the plugin to operate. The same reference ships in the plugin as docs/customizing.md.
On this page
Where the code goes
Sixteen of the eighteen hooks may be registered anywhere that runs before content is rendered: a child theme’s functions.php, a site-specific plugin, or WP-CLI. Plugin files must not be edited; updates overwrite them.
The remaining two, calucon_embed_gate_the_content_priority and calucon_embed_gate_render_block_priority, fire during plugin registration on plugins_loaded, before themes load. A callback registered in functions.php is never invoked and no error is raised. Both require an mu-plugin:
<?php
/**
* Plugin Name: Embed gate tweaks
* Description: Site-specific adjustments to the embed gate.
*/
// wp-content/mu-plugins/embed-gate-tweaks.php
// Files directly inside mu-plugins/ load before
// plugins_loaded, which is early enough for
// every hook on this page.
add_filter(
'calucon_embed_gate_the_content_priority',
function ( $priority ) {
return 60;
}
);calucon_embed_gate_providers is unaffected: the provider registry resolves lazily, on first use, so theme-registered descriptors reach it.
The two shared arguments
Received by most hooks. calucon_embed_gate_payload receives $provider without $ctx.
$provider — the descriptor
A normalised provider descriptor. Every key is present regardless of what the source descriptor set; defaults are applied before any consumer sees it.
Descriptor keys (19)
idstring- Stable identifier,
genericfor unrecognised hosts. The field to branch on — it is never translated, never empty and not overridable by the site owner. labelstring- Human-readable name, shown in the panel and the settings table. Never translated — provider names are proper nouns, so this is stable across languages.
enabledbool- Whether the site owner has this provider switched on.
kindstring- One of
video,map,audio,social,form,calendar,document,image,3d, or empty. Chooses the optional button glyph. matcharray- How URLs are recognised:
iframe_host,iframe_path,iframe_query,script_host,script_path. strategystringiframeorscript— which shape of embed this is.load_host,load_path,load_query- The privacy-preserving target used after the click, when the provider offers one. This is how Vimeo gets
dnt=1. Null or empty means “load the URL the embed carried”. fallbackstring- Template for the no-JavaScript link, with
{id}-style tokens filled from the match’s named captures. privacy_urlstring|null- The provider’s own privacy policy. Shown in the panel only when the site owner enables that link.
controllerstring|null- Who operates the service, as a plain name and place.
note,actionstring- Default panel text and button text before the wording hooks run. These two are translated, and the site owner can override both from the settings screen — so their contents are not something to compare against.
aspectstring|null- Declared aspect ratio such as
16/9, used to reserve space so the page does not jump when the embed loads. iframe_allowstring|null- Permissions policy applied to the rebuilt iframe.
autoplayis stripped from it regardless. companion_class,companion_fallback,scrub_hint_hosts- For script embeds: the sibling markup that belongs to the same embed, how to build its fallback link, and which CDN hosts must have their resource hints removed.
Branch on id. note and action are translated, and both can be overridden by the site owner, so neither is reliable for comparison. label and controller are not translated. id is never empty.
Authoritative list and defaults: src/Providers/Provider.php, in normalize().
$ctx — where the embed was found
The integration context. Keys differ by path: four exist only for block-rendered embeds, and the admin preview carries only integration. Test with isset() or ! empty() before reading any key except integration.
Context keys (7)
integrationstring, always present- Which path found the embed:
render_block,the_content,comment,widget,description,output_bufferoradmin-preview. post_idint|null- The post being rendered, on the
render_block,the_contentandcommentpaths. Null for widgets, descriptions and whole-page buffering, which have no single post. Absent entirely onadmin-preview. blockstring|null- The block name, on
render_blockonly. Null on the other content paths, absent onadmin-preview. force_gatebool, render_block only- The editor’s Always gate override. When true the detection rules skip
calucon_embed_gate_should_gatealtogether. posterstring, render_block only- A poster image URL chosen in the editor, present only when one is set and only after the server has confirmed it resolves to this site.
note_text,action_textstring, render_block only- Per-block overrides from the editor, present only when set. Capped at 400 and 120 characters respectively — note that the equivalent per-provider settings are capped at 500.
What runs when
Order of execution for a single embed, from detection to markup.
- Detection selects a candidateAn iframe, script, object or image on a host that is not the site’s.
- Gate, or allow throughis_own_host · own_hosts · www_equivalence · should_gateLast point at which an embed can be allowed through. Blocks marked Always gate bypass
should_gate. - Provider resolutionproviders (once per request) · provider_for_urlDetermines name, load target and fallback link. Does not affect gating.
- Panel construction beginsbefore_renderNo markup exists yet.
- Text resolutionnote_text · action_text · fallback_urlPlain text in, plain text out. Escaping occurs after these.
- Payload and markuppayload · placeholder_htmlThe payload is what the browser rebuilds from. The HTML is printed unescaped.
- Embed gatedembed_gatedFired once the panel exists. The front-end assets are enqueued at the same point.
- Activation, in the browserNo PHP hook runs on activation, and no JavaScript event is dispatched. See front-end JavaScript.
Describing a provider
Determine how an embed is described: its name, its privacy-preserving load target and its fallback link. Neither affects whether an embed is gated.
calucon_embed_gate_providers
Register or replace provider descriptors.
calucon_embed_gate_providersapply_filters( 'calucon_embed_gate_providers', array $providers )
filter1 argument
Parameters
$providersarray- The built-in descriptors, already normalised and already passed through the plugin’s translator.
Owner-defined rows and the per-provider settings are applied after this filter. A site owner’s notice text, button text, privacy URL and enabled flag therefore override a code-registered descriptor.
Compare on id. The note and action values are translated before this filter runs; label and controller are not.
Example
add_filter(
'calucon_embed_gate_providers',
function ( array $providers ) {
$providers[] = array(
'id' => 'example-videos',
'label' => 'ExampleVideos',
'kind' => 'video',
'match' => array(
'iframe_host' => array( 'embed.example.com' ),
// Named captures interpolate,
// URL-encoded, into load_path
// and fallback as {id}-style
// tokens.
'iframe_path' => '#^/v/(?<id>[A-Za-z0-9_-]+)#',
),
// Optional privacy-preserving
// target, used after the click.
'load_host' => 'embed-nocookie.example.com',
'load_path' => '/v/{id}',
'fallback' => 'https://example.com/watch/{id}',
'controller' => 'Example Ltd, Berlin, Germany',
'privacy_url' => 'https://example.com/privacy',
'aspect' => '16/9',
);
return $providers;
}
);Fired at src/Plugin.php:279. Permalink
calucon_embed_gate_provider_for_url
Override the descriptor matched to a URL.
calucon_embed_gate_provider_for_urlapply_filters( 'calucon_embed_gate_provider_for_url', array $provider, string $url, string $host )
filter3 arguments
Parameters
$providerarray- The descriptor the registry matched. For an unrecognised host this is the generic one.
$urlstring- The embed URL as found in the content — except from
resolve_for_asset_host(), which passes a synthetichttps://host/. See the note. $hoststring- That URL’s host, lower-cased.
Affects only how an embed is described. Gating is unaffected: an unrecognised host is gated either way.
One caller hands over a URL that never appeared in the markup. Matching a provider by asset host alone — a stylesheet, or since 1.0.0 the check that stops a known provider from hiding behind a /wp-content/ path — has only a host to work with, so it synthesises https://host/ for this filter. Code here that reads $url for a path, a query string or an id must tolerate that: match on $host first, and treat a bare origin as no information rather than as a URL that failed to match.
Example
add_filter(
'calucon_embed_gate_provider_for_url',
function ( array $provider, $url, $host ) {
// One tenant's videos share a host with
// everyone else's, so the label has to
// come from the path.
if ( 'generic' === $provider['id']
&& false !== strpos( $url, '/studio-a/' ) ) {
$provider['label'] = 'Studio A';
}
return $provider;
},
10,
3
);Fired at src/Plugin.php:427. Permalink
Wording and links
Plain text in, plain text out. Escaping occurs after these hooks; a pre-escaped return value reaches the visitor as entities.
calucon_embed_gate_note_text
Replace the notice text on a panel.
calucon_embed_gate_note_textapply_filters( 'calucon_embed_gate_note_text', string $note, array $provider, array $ctx )
filter3 arguments
Parameters
$notestring- The notice text as resolved so far: the provider default, or the site owner’s override if one is set.
$providerarray- The descriptor. See the shared arguments.
$ctxarray- Where this embed was found. See the shared arguments.
The plugin is a technical measure. Notice text should not assert a legal outcome.
Example
add_filter(
'calucon_embed_gate_note_text',
function ( $note, array $provider, array $ctx ) {
// Plain text in, plain text out. The
// panel escapes this, so an & here
// would reach the visitor as
// "&amp;".
if ( 'youtube' === $provider['id'] ) {
return 'Video hosted by YouTube. It loads when you '
. 'choose to load it.';
}
return $note;
},
10,
3
);Fired at src/Plugin.php:449. Permalink
calucon_embed_gate_action_text
Replace the button label on a panel.
calucon_embed_gate_action_textapply_filters( 'calucon_embed_gate_action_text', string $action, array $provider, array $ctx )
filter3 arguments
Parameters
$actionstring- The button text resolved so far.
$providerarray- The descriptor.
$ctxarray- The integration context.
The label is the accessible name of a <button> and should describe the action, for example “Load this video”.
Example
add_filter(
'calucon_embed_gate_action_text',
function ( $action, array $provider, array $ctx ) {
// $ctx['integration'] is the one key
// that is always present, whichever
// path the embed came in on.
if ( 'comment' === $ctx['integration'] ) {
return 'Load this embed';
}
return $action;
},
10,
3
);Fired at src/Plugin.php:452. Permalink
calucon_embed_gate_fallback_url
Replace the target of the no-JavaScript link.
calucon_embed_gate_fallback_urlapply_filters( 'calucon_embed_gate_fallback_url', string $url, array $provider, array $ctx )
filter3 arguments
Parameters
$urlstring- The fallback the descriptor produced, usually built from the embed’s own id.
$providerarray- The descriptor.
$ctxarray- The integration context.
This link is the only route to the content when JavaScript is unavailable.
Example
add_filter(
'calucon_embed_gate_fallback_url',
function ( $url, array $provider, array $ctx ) {
// Prefer your own write-up over the
// provider's page, when the post has
// one. post_id is absent on the widget,
// description and output-buffer paths.
if ( ! empty( $ctx['post_id'] ) ) {
$own = get_post_meta(
$ctx['post_id'],
'embed_writeup_url',
true
);
if ( is_string( $own ) && '' !== $own ) {
return $own;
}
}
return $url;
},
10,
3
);Fired at src/Plugin.php:455. Permalink
Markup and payload
The inverse of the wording hooks: return values are printed unescaped. Escape any interpolated value.
calucon_embed_gate_placeholder_html
Rewrite the rendered panel markup.
calucon_embed_gate_placeholder_htmlapply_filters( 'calucon_embed_gate_placeholder_html', string $html, array $provider, array $ctx )
filter3 arguments
Parameters
$htmlstring- The rendered panel.
$providerarray- The descriptor.
$ctxarray- The integration context.
Four parts are contractual and are located by class: the cg-embed container with its data-cg-* attributes, the cg-embed__panel div directly inside it, a <button type="button" class="cg-embed__button">, and the cg-embed__fallback paragraph.
Must not emit a literal <iframe: gated content may be processed again, and a raw iframe is re-detected. To replace the markup wholesale, override templates/placeholder.php in the theme.
Example
add_filter(
'calucon_embed_gate_placeholder_html',
function ( $html, array $provider, array $ctx ) {
if ( 'video' !== $provider['kind'] ) {
return $html;
}
// Wrapping keeps every class and
// attribute the front end relies on
// intact. Whatever is returned is
// printed, so the caption is escaped
// here.
$caption = sprintf(
/* translators: %s: provider name. */
__( 'Hosted by %s.', 'my-theme' ),
$provider['label']
);
return '<figure class="video-embed">'
. $html
. '<figcaption>' . esc_html( $caption )
. '</figcaption></figure>';
},
10,
3
);Fired at src/Plugin.php:434. Permalink
calucon_embed_gate_payload
Adjust the JSON the browser rebuilds from.
calucon_embed_gate_payloadapply_filters( 'calucon_embed_gate_payload', array $payload, array $provider )
filter2 arguments
Parameters
$payloadarray- What the browser rebuilds the embed from:
src,attrs, and depending on the embedsrcdoc,tag,strategyorinline. See the payload. $providerarray- The descriptor.
No $ctx is passed. Use placeholder_html where the integration context is required.
attrs is an array when attributes are present and an empty stdClass otherwise, so that it encodes as {} rather than []. Check with is_array() before writing to it.
Example
add_filter(
'calucon_embed_gate_payload',
function ( array $payload, array $provider ) {
// attrs is an empty stdClass when the
// embed carried no attributes, so it
// cannot be indexed blindly.
if ( ! is_array( $payload['attrs'] ) ) {
$payload['attrs'] = array();
}
if ( ! isset( $payload['attrs']['title'] ) ) {
$payload['attrs']['title'] = sprintf(
/* translators: %s: provider name. */
__( 'Embedded content from %s', 'my-theme' ),
$provider['label']
);
}
return $payload;
},
10,
2
);Fired at src/Plugin.php:437. Permalink
Plumbing
Ordering and asset versioning. The two priority filters are the only hooks on this page that cannot be used from a theme.
calucon_embed_gate_cmp_config
Adjust or disable the consent-platform bridge.
calucon_embed_gate_cmp_configapply_filters( 'calucon_embed_gate_cmp_config', ?array $config, array $cmp_options )
filter2 arguments
Parameters
$configarray|null- The built config, or
nullwhen the bridge is disabled or no tested platform was detected. $cmp_optionsarray- The
cmpsection of the settings:bridge,borlabs_group.
Disabling the bridge is restrictive rather than permissive: the server always renders the panel, so gated embeds then require a click.
Example
add_filter(
'calucon_embed_gate_cmp_config',
function ( $config, array $cmp_options ) {
// null means the bridge is already off.
// Leave it off.
if ( ! is_array( $config ) ) {
return $config;
}
// This site's platform files embeds
// under its own category name rather
// than the default.
$config['category'] = 'external-media';
return $config;
},
10,
2
);Fired at src/Plugin.php:763. Permalink
calucon_embed_gate_the_content_priority
Set the gate’s priority on the_content.
calucon_embed_gate_the_content_priorityapply_filters( 'calucon_embed_gate_the_content_priority', int $priority )
filter1 argument
Parameters
$priorityint- The default,
20.
Not usable from a theme. Registration occurs on plugins_loaded, which precedes theme loading, so a callback added in functions.php never runs and no error is raised. Use an mu-plugin; see where the code goes.
Example
// wp-content/mu-plugins/embed-gate-priority.php
// An mu-plugin, not functions.php: this filter
// fires while the plugin registers on
// plugins_loaded, before any theme loads.
add_filter(
'calucon_embed_gate_the_content_priority',
function ( $priority ) {
// Run after a plugin that rewrites
// content at 50.
return 60;
}
);Fired at src/Integration/TheContent.php:37. Permalink
calucon_embed_gate_render_block_priority
Set the gate’s priority on render_block.
calucon_embed_gate_render_block_priorityapply_filters( 'calucon_embed_gate_render_block_priority', int $priority )
filter1 argument
Parameters
$priorityint- The default,
10.
Not usable from a theme, for the reason given under the_content_priority.
Example
// wp-content/mu-plugins/embed-gate-priority.php
add_filter(
'calucon_embed_gate_render_block_priority',
function ( $priority ) {
// Let a block-rewriting plugin at 15 go
// first.
return 20;
}
);Fired at src/Integration/RenderBlock.php:38. Permalink
calucon_embed_gate_asset_version
Replace the version string on bundled assets.
calucon_embed_gate_asset_versionapply_filters( 'calucon_embed_gate_asset_version', string $version, string $relative )
filter2 arguments
Parameters
$versionstring- The plugin version, or the file’s modification time on a site the plugin considers a development one.
$relativestring- The asset path inside the plugin, for example
assets/js/gate.js.
On a multi-server site the default modification time differs between machines, so one file can be served under several version strings. A build identifier is stable across servers.
Example
add_filter(
'calucon_embed_gate_asset_version',
function ( $version, $relative ) {
// One deploy identifier for every
// server behind the load balancer,
// instead of each machine's own mtime.
if ( defined( 'MY_DEPLOY_ID' ) ) {
return (string) MY_DEPLOY_ID;
}
return $version;
},
10,
2
);Fired at src/Support/AssetVersion.php:67. Permalink
Actions
Return nothing. They run on every page view and must not write to the visitor’s browser.
calucon_embed_gate_before_render
Runs before a panel’s markup is built.
calucon_embed_gate_before_renderdo_action( 'calucon_embed_gate_before_render', array $provider, array $ctx )
action2 arguments
Parameters
$providerarray- The descriptor about to be rendered.
$ctxarray- The integration context.
Runs for every panel on every page view. Callbacks must be inexpensive, and must not write to client storage.
Example
add_action(
'calucon_embed_gate_before_render',
function ( array $provider, array $ctx ) {
// Remember which kinds of embed this
// page holds, so the footer can enqueue
// only the styles it needs.
if ( ! isset( $GLOBALS['my_embed_kinds'] ) ) {
$GLOBALS['my_embed_kinds'] = array();
}
$kind = '' !== $provider['kind']
? $provider['kind']
: 'generic';
$GLOBALS['my_embed_kinds'][ $kind ] = true;
},
10,
2
);Fired at src/Plugin.php:446. Permalink
calucon_embed_gate_embed_gated
Runs once an embed has been gated.
calucon_embed_gate_embed_gateddo_action( 'calucon_embed_gate_embed_gated', array $provider, array $ctx )
action2 arguments
Parameters
$providerarray- The descriptor that was gated.
$ctxarray- The integration context.
Differs from before_render in timing: before_render runs as a panel begins to be built; this runs once the embed has been gated.
Example
add_action(
'calucon_embed_gate_embed_gated',
function ( array $provider, array $ctx ) {
// A per-request tally, for a debug bar
// or a footer note. Deliberately
// in-memory: writing on a render would
// make every page view a database
// write.
if ( ! isset( $GLOBALS['my_gated_embeds'] ) ) {
$GLOBALS['my_gated_embeds'] = array();
}
$id = $provider['id'];
$GLOBALS['my_gated_embeds'][ $id ] = 1 + (int) (
isset( $GLOBALS['my_gated_embeds'][ $id ] )
? $GLOBALS['my_gated_embeds'][ $id ]
: 0
);
},
10,
2
);Fired at src/Plugin.php:503. Permalink
calucon_embed_gate_flush_caches
Runs after the plugin clears its caches.
calucon_embed_gate_flush_cachesdo_action( 'calucon_embed_gate_flush_caches' )
actionno arguments
Intended for caches the plugin cannot detect: a hosting layer, a CDN, a custom fragment cache. A stale page continues to serve panels built from superseded settings.
Do not assume a settings save: the same action arrives on activation and deactivation, when what is cached is a page with no gate at all, and after an update, when it is the previous version’s placeholder markup. A callback that purges is right in every case; one that reads the current settings to decide what to purge is not.
Example
add_action(
'calucon_embed_gate_flush_caches',
function () {
// No arguments: add_action's default
// $accepted_args of 1 is fine because
// the callback declares none.
if ( function_exists( 'my_host_purge_everything' ) ) {
my_host_purge_everything();
}
}
);Fired at src/Support/CacheFlush.php:54. Permalink
Hooks that can stop the gate
Effect. Each of these can cause a third party to be requested on page load, for every visitor, with no panel and no click. For those embeds the gate is absent, not weakened.
The Own hosts and Never gate settings are equivalent to two of these and remain visible in the admin.
calucon_embed_gate_should_gatecan disable the gate
Gate a URL, or allow it to load with the page.
calucon_embed_gate_should_gatecan disable the gateapply_filters( 'calucon_embed_gate_should_gate', bool $gate, string $url, array $ctx )
filter3 arguments
Parameters
$gatebool- What the gate intends to do. In practice always
truehere — the filter runs on embeds it has already decided to hold. $urlstring- The URL that would be requested.
$ctxarray- The integration context.
Blocks set to Always gate bypass this filter: force_gate is evaluated before the callback runs.
The Never gate setting expresses a host exemption in the admin, where it remains visible to the site owner.
Example
add_filter(
'calucon_embed_gate_should_gate',
function ( $gate, $url, array $ctx ) {
// Tightening, not loosening: hold
// everything that reaches a sidebar
// widget, whatever the settings say.
if ( 'widget' === $ctx['integration'] ) {
return true;
}
return $gate;
},
10,
3
);Fired at src/Plugin.php:468. Permalink
calucon_embed_gate_is_own_hostcan disable the gate
Classify a host as the site itself.
calucon_embed_gate_is_own_hostcan disable the gateapply_filters( 'calucon_embed_gate_is_own_host', bool $own, string $host )
filter2 arguments
Parameters
$ownbool- What the built-in rules concluded: the home and site URLs, since 1.0.0 the site’s asset bases as well —
content_url(),includes_url(),plugins_url(), the uploads base and both theme directory URIs — the network’s other sites on multisite, and the configured own-hosts and never-gate lists. $hoststring- The host being classified, lower-cased.
The always-gate list takes precedence and is evaluated first: hosts on it return false without reaching this filter.
Equivalent to the Own hosts setting, which requires no code.
This filter classifies a host, and 1.0.0 added one rule that classifies a URL instead: a <script> or <link rel=stylesheet> whose path contains /wp-content/ or /wp-includes/ is left alone whatever host serves it, for the CDN that rewrites the finished HTML rather than filtering WordPress’s URL functions. It does not run this filter, so returning false here will not gate such a URL. Two things do: the host belonging to a provider the plugin already knows, and the always-gate list. Never applies to iframes or images.
Example
add_filter(
'calucon_embed_gate_is_own_host',
function ( $own, $host ) {
// This CDN serves this site's own media
// library, so it is not a third party.
// Anything added here is requested on
// page load, with no panel and no
// click.
return $own || 'assets.my-cdn.example' === $host;
},
10,
2
);Fired at src/Plugin.php:413. Permalink
calucon_embed_gate_own_hostscan disable the gate
Add hosts treated as the site itself.
calucon_embed_gate_own_hostscan disable the gateapply_filters( 'calucon_embed_gate_own_hosts', array $hosts )
filter1 argument
Parameters
$hostsarray- The configured Own hosts and Never gate lists, merged. Both have the same effect here; they are separate settings because they mean different things to the site owner.
Hosts added here are not gated, and are requested on page load. The Own hosts setting is equivalent and remains visible to the site owner.
The list this filter receives is already populated. Since 1.0.0 it carries the host of every URL WordPress reports as the site’s own: home_url(), site_url(), content_url(), includes_url(), plugins_url(), the uploads base and both theme directory URIs — plus every domain on a multisite network. A CDN plugin that filters those functions therefore declares its host as the site’s own already, and needs nothing added here.
Those functions can only ever answer where do my assets live, so none of them can introduce a third party. This filter can: a host added here is exempt from gating on every page, for iframes as much as for scripts.
Example
add_filter(
'calucon_embed_gate_own_hosts',
function ( array $hosts ) {
// Declared in code because it is
// deploy-specific: the bucket name
// differs between staging and
// production.
if ( defined( 'MY_MEDIA_HOST' ) ) {
$hosts[] = MY_MEDIA_HOST;
}
return $hosts;
}
);Fired at src/Plugin.php:828. Permalink
calucon_embed_gate_www_equivalencecan disable the gate
Treat www. and the bare domain as one site.
calucon_embed_gate_www_equivalencecan disable the gateapply_filters( 'calucon_embed_gate_www_equivalence', bool $equivalent )
filter1 argument
Parameters
$equivalentbool- The configured value.
Disabling this changes classification in both directions: a host previously treated as the site becomes a third party, and on a site whose canonical host carries www the bare domain is no longer recognised as the site itself.
Example
add_filter(
'calucon_embed_gate_www_equivalence',
function ( $equivalent ) {
// Only on a site where www and the bare
// domain really are separate
// properties. Check both spellings with
// `wp calucon-embed-gate scan` after
// changing this.
return false;
}
);Fired at src/Plugin.php:405. Permalink
Settings
Option schema, ranges and enums.
One option, calucon_embed_gate_options, sanitised against a schema on every read. Malformed input falls back to defaults. Omitted keys revert to their defaults; there is no partial merge.
wp option get calucon_embed_gate_options --format=json
wp option patch update calucon_embed_gate_options detection images 1
wp option patch update calucon_embed_gate_options consent memory sessionproviders.{id}enabled,privacy_variant(bool);note,action(strings, tags stripped, max 500 characters);privacy_url(https only, max 500). Keys must match^[a-z0-9_-]{1,64}$.custom_providers- Owner-defined rows:
id(custom-<slug>, generated once),label(max 80),hosts,script_hosts,kind. Max 100 rows, 50 hosts each; wildcards are rejected here, and hosts a built-in already handles are stripped. detectioniframes,scripts,images(images off by default),www_equivalence,output_buffer(bool);own_hosts,never_gate,always_gate— host lists where a leading*.wildcard is allowed. A pasted full URL is reduced to its host for you.displayprivacy_link(bool, off by default) — show each provider’s own privacy policy inside the panel.appearance- Shape:
preset(default|minimal|card),corners(''|square|rounded|pill|custom),radius(0–48),border_width(0–10, stored as a string),shadow,density,align.
Button:button_style,button_size,button_width,hover,play_icon,note_size,withdraw_style.
Poster:poster_panel,poster_dim.
Colour:bg,fg,accent,accent_fg,link,border_color, plusdarkanddark_bg/dark_fg/dark_accent/dark_accent_fgapplied only underprefers-color-scheme: dark. consentmemory(off|session|persistent, default off),scope(embed|provider|all, defaultprovider),duration_days(1–730, default 180).cmpbridge(bool, off),borlabs_group(slug, defaultexternal-media). An experimentaltcfkey existed before 1.0.0 and was removed with the IAB TCF bridge; a stored value is ignored.
Colour values take one of three forms only: a 3- or 6-digit hex such as #2271b1, the string preset:<slug> to follow a theme palette colour by name, or empty to inherit. Alpha hex, rgb() and named colours are rejected and fall back to inherit.
No other storage: no transients, no post meta, no user meta. Uninstalling deletes the option on every site of a network. Post content is never rewritten; gating happens at render time.
Authoritative schema: src/Support/Options.php, in defaults() and sanitize_tree(). Cache plugins are flushed automatically whenever this option changes.
WP-CLI
Two commands, both read-only.
Two subcommands under wp calucon-embed-gate. Both are read-only and make no outbound request; scan renders content in memory and reads the resulting markup.
wp calucon-embed-gate scan --format=json
wp calucon-embed-gate scan --posts=200
wp calucon-embed-gate providers --format=jsonscan- Renders recent published posts and pages through the same pipeline the front end uses, and classifies every embed it finds. Columns:
host,tag,label,status,count,first_seen,url. --posts=<number>- How many recent posts and pages to render. Default 50, and clamped to 1–500 — a limit the command’s own help text does not mention.
--format=<format>table(default),json,csv,yamlorcount. Both subcommands accept it, and it is the only flagproviderstakes.providers- Lists providers as the gate resolves them — built-ins, then anything registered through the filter, then the owner’s overrides. Columns:
id,label,enabled,strategy,hosts,load_host,controller,privacy_url.
Every third-party row in scan output should read gated. The remaining statuses indicate an embed that loads without a click because a setting permits it: rule-disabled, provider-disabled, own-host. no-usable-url indicates markup with nothing to gate.
Widgets, template parts and page-builder layouts are outside the scan, as on the admin Status screen. scan runs the full the_content chain, so other plugins hooked there execute during it; the gate itself makes no request.
Withdrawal button
Clears remembered consent.
A shortcode and a block, rendering identical markup through the same code. Discards consent stored in the browser.
[calucon_embed_gate_withdraw]
[calucon_embed_gate_withdraw label="Forget my embed choices"]labelstring- Button text. Defaults to a translated “Withdraw embed consents”.
- Block equivalent
calucon-embed-gate/withdraw, with the same singlelabelattribute. It is a dynamic block rendered on the server, so the two paths cannot drift apart.- Rendered markup
- A
<button type="button" class="cg-withdraw" data-cg-withdraw>plus a<span class="cg-withdraw__status" role="status" aria-live="polite">. The button’saria-controlspoints at that span, whose id is generated per render — so never hard-code it.
Rendering it enqueues the front-end assets even on a page with no embeds, so it functions on a privacy policy page. It clears both storages unconditionally; with consent memory off nothing is stored and the control is inert.
Placeholder markup
The classes a theme may build on.
The shape the front-end script, the CMP bridge and the test suite depend on. Versioned as public API.
<div class="cg-embed" role="group" aria-label="..."
data-cg-provider="youtube" data-cg-host="www.youtube.com"
data-cg-payload="{...}" style="--cg-aspect:16/9">
<div class="cg-embed__panel">
<p class="cg-embed__note">...</p>
<button type="button" class="cg-embed__button">...</button>
<p class="cg-embed__fallback"><a href="..." rel="noopener nofollow">...</a></p>
<p class="cg-embed__privacy"><a href="..." rel="noopener nofollow">...</a></p>
</div>
</div>.cg-embed- The container. Must carry
data-cg-payloadas well as the class — the script’s selector is both together. .cg-embed__panel- Must be a
<div>and a direct child of the container. The script checks the parent relationship, not just the class. .cg-embed__button- A real
<button type="button">. Not a link, not a div — this is the control the whole design rests on. .cg-embed__fallback- A
<p>wrapping the link that works without JavaScript. Find it by class, never by position. Since 0.10.0 a privacy link can follow it, so “the last link in the panel” is now the wrong one. .cg-embed__privacy- Present only when the provider declares a policy URL and the site owner has enabled the link. Do not assume it exists.
.cg-embed--poster/.cg-embed__poster- Container modifier and image class when a poster is set. The image must keep
alt=""andaria-hidden="true"— the group is already named — and the script removes it on activation by that class. .cg-embed--silent- A companion script or stylesheet that belongs to another embed’s panel. Hidden, and activated together with the panel it belongs to. New in 0.11.0.
.cg-embed--active- Added by the script after activation. Not server-rendered.
.cg-embed__status,.cg-embed__error- Live regions created by the script when loading starts and when it fails. Also not server-rendered — do not put them in a template.
The panel is named with role="group" and an aria-label. Do not substitute a heading: the correct level cannot be determined from inside an embed, which may follow an h1 on a single post and an h2 on an archive. A named group supplies the name without altering the document outline.
Data attributes
Attributes the front end reads.
data-cg-provider- The provider id. Used for consent-memory keys, for matching silent companions to their panel, and for the button glyph.
data-cg-host- The host the embed will contact; omitted when unknown. It is what stops consenting to one unrecognised widget from loading a different one.
data-cg-payload- JSON the browser rebuilds the embed from. See below.
data-cg-activated"1"once activation has begun; guards against activating twice. Removed again if loading fails.data-cg-bridged"1"when a consent platform activated this embed rather than the visitor. That distinction is what lets a withdrawal re-gate exactly those and leave real clicks alone.data-cg-fallback- The fallback URL, stashed on the container before the panel is removed so the error state can still offer a link.
data-cg-withdraw- Marks the withdrawal control. Clicks are delegated from any ancestor carrying it.
Admin screens use further data-cg-* attributes. These are internal and may change without notice.
The payload
srcstring- The URL to load after consent. Absent for a
srcdocembed and for an inline loader. attrsobject- Attributes re-applied to the rebuilt element, filtered to a safelist:
title,width,height,sandbox,loading,allow,allowfullscreen,referrerpolicy,type,id,name,class,data-secret,security,alt.autoplayis stripped fromallow. In PHP this is an emptystdClasswhen there are no attributes. srcdocstring- Present only for a
srcdocembed, carrying the original inline document to restore on click. tagstring- Present only when the element is not an iframe:
embed,object,imgorlink. strategystring- Present only when not
iframe.scriptis the case the front end branches on. inlinestring- The page’s own inline loader text, re-run after consent instead of fetching a URL. New in 0.11.0.
CSS custom properties
Seven properties that restyle the panel.
Set on .cg-embed to restyle the panel; no !important is required. The Appearance tab writes the same properties and includes a contrast check.
.cg-embed {
--cg-bg: #f6f7f7;
--cg-fg: #1d2327;
--cg-accent: #2271b1;
--cg-accent-fg: #ffffff; /* keep >= 4.5:1 against --cg-accent */
--cg-radius: 8px;
--cg-gap: .75rem;
--cg-font: inherit;
}--cg-bg- Panel background. Defaults to the theme’s
basepalette colour. --cg-fg- Panel text, links and the focus outline. Defaults to the theme’s
contrast. --cg-accent- Button background and border.
--cg-accent-fg- Button text. It pairs with
--cg-accent, not with--cg-bg— that is the pair to keep above 4.5:1. --cg-radius- Corner radius of the container, poster and buttons. Default
4px. --cg-gap- Panel padding and the gap between its parts. Default
0.75rem. --cg-font- Font family on the container. Default
inherit. --cg-aspect- Set by the server, per embed, inline. It reserves the embed’s space so the page does not jump when it loads. Leave it alone.
The cg- prefix and the --cg-* and data-cg-* names were retained through the plugin’s rename; stylesheets written against earlier versions still apply.
Template variables
Variables passed to the template.
To replace the panel markup, copy the plugin’s template into the theme:
{your-theme}/calucon-embed-gate/placeholder.php$provider,$ctx- As described above.
$aria_label- The accessible name for the panel.
$note,$action- Panel text and button text. Plain text — escape them.
$fallback_url,$fallback_label- The no-JavaScript link and its text.
$privacy_url,$privacy_label- The provider’s policy link, or empty when there is none or the site owner disabled it.
$payload_attr- The
data-cg-payloadvalue, already HTML-escaped — print it as-is. $aspect- A CSS aspect ratio such as
16/9, or empty. Set it as--cg-aspectso the page does not reflow. $host- The host the embed will contact, or empty.
$poster- A site-origin poster URL, or empty. Never substitute a provider-hosted image.
The template runs outside WordPress in the fixture suite, so it uses htmlspecialchars() rather than esc_attr(). A theme copy may use the WordPress functions.
Block attributes
The five attributes the editor adds.
The editor adds a panel to core/embed, core/html, core/video and core/audio. core/html is included because pasted iframe markup lands there.
caluconEmbedGatestring- Empty for the site default,
always, ornever.alwayssetsforce_gateand skipsshould_gate. caluconEmbedGatePosternumber- An attachment id. The server resolves it and returns nothing unless the resulting URL is on this site — so a CDN-offloaded media library needs that host declared under Own hosts.
caluconEmbedGatePosterUrlstring- The inspector’s preview only. The id above is what the server renders from.
caluconEmbedGateActionstring- Per-block button text, capped at 120 characters.
caluconEmbedGateNotestring- Per-block notice text, capped at 400 characters.
These caps differ from the 500-character cap on the equivalent per-provider settings. Values travel as block attributes; nothing is stored in post meta.
Front-end JavaScript
Ready hooks, bridge, consent storage.
No custom event is dispatched. Activation fires nothing a script can listen for. The available surface is a hook map and a bridge object.
window.caluconEmbedGateReadyHooks
Some provider SDKs render only the placeholders present when they run, so an embed injected after consent must be re-scanned. The plugin ships hooks for Strava, Twitter, Instagram and Facebook. A hook registered under an existing id replaces the built-in.
window.caluconEmbedGateReadyHooks =
window.caluconEmbedGateReadyHooks || {};
// Keyed by provider id. Runs after the SDK loads, and again after
// each later activation of the same provider. Registration order
// does not matter: before or after gate.js are both fine.
window.caluconEmbedGateReadyHooks['example-videos'] = function () {
if ( window.ExampleVideos && window.ExampleVideos.scan ) {
window.ExampleVideos.scan();
}
};Each hook is wrapped in try/catch; a failing hook does not halt the page.
window.caluconEmbedGateBridge
The interface the consent-platform bridge is built on. each() walks the gated panels, grant() activates one, grantAll() activates all, and regate() re-gates those the bridge opened — identified by data-cg-bridged — and not those a visitor clicked.
Intended only for integrating a consent platform the plugin does not detect. grantAll() on page load would load every embed for every visitor.
Consent memory
Off by default. When enabled the browser stores one key; the server is not informed, as a server-side consent state would make every page uncacheable.
- Storage key
calucon-embed-gate, insessionStoragewhen memory is set to session andlocalStorageotherwise.- Value
{"v":1,"g":{…}}— a schema version and a map of grant keys to timestamps.- Grant keys
*for scope all;p:<provider>for scope provider, with@<host>appended for generic providers so one unknown widget does not unlock another;e:<url>for scope embed. No identifier of any kind, only what was consented to.- Expiry
- Checked when read, not on a timer. Session memory lasts the session.
- Writes
- Only on a real click. Page-load code reads storage; it never writes. Restoring from memory and bridge grants both explicitly skip the write.
Constants
Three defined constants.
CALUCON_EMBED_GATE_VERSION- The plugin version string.
CALUCON_EMBED_GATE_FILE- Absolute path to the main plugin file. Use it with
plugins_url()to build an asset URL. CALUCON_EMBED_GATE_DIR- Absolute path to the plugin directory.
Those three only. There is no CALUCON_EMBED_GATE_URL; URLs are derived at each call site from CALUCON_EMBED_GATE_FILE via plugins_url().
What is promised
Semver coverage.
The plugin follows semantic versioning, and 1.0.0 turned the coverage into a written promise, pinned by a test in the plugin’s own suite (StabilityContractTest). Stable across minor releases:
- The placeholder markup contract — the classes, roles,
data-cg-*attributes and--cg-*custom properties described above. - The filters and actions on this page.
- The template variables handed to a placeholder override.
- The settings keys under
calucon_embed_gate_options. - The WP-CLI commands and their output shape.
Deliberately not promised, because they are data rather than API: the provider descriptors (hosts, endpoints, labels) and the tested-platform lists on the Compatibility screen. Both may change in a minor release. 1.0.0 also declares the plugin feature-complete: from here the roadmap is fixes, field-validation findings and WordPress/PHP compatibility, not new surface.
The remainder is documented as observed behaviour, not as a contract. caluconEmbedGateReadyHooks, caluconEmbedGateBridge, the browser-storage format and the data-cg-payload key set appear in no plugin document; the consent-platform bridge depends on them. Pin a version if your code does.
Neither of the following is public API. Plugin::instance() does not exist: the constructor is private and no accessor is provided. PipelineFactory is part of the test suite, which is excluded from the built plugin.
What no hook can do
- Make the plugin fetch something remote. There is no supported way and there will not be. Do not fetch a thumbnail, a poster or a favicon from the provider in a filter either — that is the request the panel exists to prevent, just moved to the server. Posters must come from your own site, which the server enforces.
- Reinstate
autoplay. It is stripped fromallowafter every filter has run. It is also a WCAG 1.4.2 failure, and not what someone who pressed a button asked for. - Store anything before the click. No cookie, no
localStorage, nosessionStorage— not even to remember something helpful. - Reorder the gate past the content filters. It runs late on purpose so it sees the output of shortcodes and blocks. Page-builder output that bypasses those filters needs the Gate the whole page output setting, not custom code.
The gate fails closed: an unrecognised third-party iframe is caught by the generic rule even when a descriptor is wrong. A broken customization renders a generic panel rather than permitting the request.
Elsewhere
- Plugin overview — what it does and why.
- Customizing guide — the same ground in prose, with the reasoning rather than the signatures.
docs/customizing.mdinside the plugin folder — the same reference, shipped in the zip, so an agent working in your codebase can read it without a network.- Source on GitHub — issues, and private security reporting. Any way to make a page contact a third party before the click counts as a vulnerability.
- Live demo — every provider on one page, with nothing loading until you press a button.
- WordPress.org — install, changelog, support forum.
Generated from the plugin source; the hook list is verified against the code on every build, in both directions. Describes technical behaviour only and makes no claim regarding legal obligations.
