` and `&` as \u escapes, so no remote value can open or close a tag or comment.
*/
const JSON_FLAGS = JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_SLASHES;
/**
* The first line of our inline start scripts, for consent tools that look at a script's code (Real Cookie Banner).
*/
const MARK = '/*sranywhere*/';
/**
* Whether page.js and sr7.css are already on this page.
*
* @var bool
*/
private static $head_done = false;
/**
* Whether the page's first module has been seen for the picture preload (one per page, the first module only).
*
* @var bool
*/
private static $lcp_done = false;
/**
* The block's render callback.
*
* @param array $attributes The block's attributes.
* @return string
*/
public static function render_block( $attributes ) {
return self::render_module( (array) $attributes, true );
}
/**
* Registers the shortcode `[sr_anywhere key="ek_…" align="full" offset="header"]` for the classic editor and page
* builders without a widget (plan §6.10). Same output as the block, same checks (decision 0006).
*/
public static function register_shortcode() {
add_shortcode( 'sr_anywhere', array( self::class, 'shortcode' ) );
}
/**
* The shortcode's output.
*
* @param array|string $atts The shortcode's attributes.
* @return string
*/
public static function shortcode( $atts ) {
$atts = shortcode_atts(
array(
'key' => '',
'align' => '',
'offset' => '',
),
$atts,
'sr_anywhere'
);
return self::render_module(
array(
'embedKey' => trim( (string) $atts['key'] ),
'align' => in_array( $atts['align'], array( 'full', 'wide' ), true ) ? $atts['align'] : '',
'fsOffset' => (string) $atts['offset'],
),
false
);
}
/**
* A module where the block or the shortcode stands.
*
* @param array $attributes `embedKey`, and optionally `align` and `fsOffset`.
* @param bool $is_block Whether WordPress renders it as a block (its wrapper then carries the block supports).
* @return string
*/
private static function render_module( array $attributes, $is_block ) {
$key = isset( $attributes['embedKey'] ) ? (string) $attributes['embedKey'] : '';
if ( ! Keys::is_embed_key( $key ) || is_feed() || ! Placeable::allows( $key ) ) {
return '';
}
if ( Keys::is_group_key( $key ) ) {
return self::wrap( self::plain_line( 'g', $key ), $attributes, false, $is_block );
}
$start = Start::get( $key );
if ( Start::GONE === $start ) {
return '';
}
if ( ! is_array( $start ) ) { // no start known, or a module that plays from its embed line (Start::LINE)
return self::wrap( self::plain_line( 'i', $key ), $attributes, false, $is_block );
}
$html = self::head_inline( $start );
$html .= self::preload( $start );
$html .= self::markup( $start );
$html .= wp_get_inline_script_tag( self::start_script( $start ), array( 'id' => 'sranywhere-start-' . $key ) );
wp_enqueue_script( 'sranywhere-loader-' . $key, $start['embed'], array(), null, array( 'strategy' => 'async', 'in_footer' => true ) ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- the delivery versions its loader itself.
return self::wrap( $html, $attributes, ! empty( $start['fullHeight'] ), $is_block );
}
/**
* On pages whose content holds a module, puts page.js and sr7.css into the head before the content renders
* (classic themes print the head first). Block themes render the content before the head anyway.
*/
public static function enqueue_early() {
if ( ! is_singular() ) {
return;
}
$post = get_queried_object();
if ( ! $post instanceof \WP_Post ) {
return;
}
foreach ( Start::keys_in_content( $post->post_content ) as $key ) {
$start = Start::get( $key );
if ( is_array( $start ) ) {
self::enqueue_head( $start );
self::preload( $start, true );
return;
}
}
}
/**
* The picture the page's first module opens with, preloaded (plan §6.2): ``, in the head when the head is still to come, else right before the module. A preload only
* fetches the picture early; it paints nothing, so the slide still opens with its own animation. Only the first
* module on the page, the one most likely seen first, and only a picture on the delivery (Start::lcp()).
*
* @param array $start A parsed start.
* @param bool $in_head Whether the head is being printed now (enqueue_early(), which runs before
* wp_preload_resources() in `wp_head`).
* @return string What to print before the module ('' when it went into the head, or when there is nothing).
*/
private static function preload( array $start, $in_head = false ) {
if ( self::$lcp_done ) {
return '';
}
self::$lcp_done = true;
if ( empty( $start['lcp']['src'] ) ) {
return '';
}
$src = $start['lcp']['src'];
if ( $in_head || ! did_action( 'wp_head' ) ) {
add_filter(
'wp_preload_resources',
static function ( $resources ) use ( $src ) {
$resources[] = array(
'href' => $src,
'as' => 'image',
'fetchpriority' => 'high',
);
return $resources;
}
);
return '';
}
return '';
}
/**
* Adds the optimizer marks and the consent-tool marks to our own scripts (decisions 0007, 0023): the delivery works
* like a CDN for the page's own content, sets no cookies, and since 2026-10-07 sends nothing to Google (0023's
* condition, confirmed by sr-anywhere). The same marks the classic plugin uses: Cookiebot, CookieYes, Borlabs.
*
* @param array $attributes A script tag's attributes.
* @return array
*/
public static function script_attributes( $attributes ) {
if ( isset( $attributes['id'] ) && 0 === strpos( (string) $attributes['id'], 'sranywhere-' ) ) {
$attributes['data-no-optimize'] = '1';
$attributes['data-no-defer'] = '1';
$attributes['data-cfasync'] = 'false';
$attributes['data-jetpack-boost'] = 'ignore';
$attributes['data-cookieconsent'] = 'ignore';
$attributes['data-wcc'] = 'necessary';
$attributes['data-borlabs-cookie-script-blocker-ignore'] = true;
}
return $attributes;
}
/**
* Keeps sr7.css out of combined stylesheets (decision 0007; the matrix of 2026-10-08): LiteSpeed Cache fetches an
* external stylesheet to combine it and leaves it out when that fetch fails, so the printed start has no styles and
* the page jumps until the cache is purged.
*
* @param string $tag The stylesheet's link tag.
* @param string $handle The style's handle.
* @return string
*/
public static function style_tag( $tag, $handle ) {
if ( self::CSS_HANDLE === $handle && false === strpos( $tag, 'data-no-optimize' ) ) {
$tag = (string) preg_replace( '/disableBlocking();
}
return $is_blocked;
}
/**
* SiteGround Speed Optimizer ignores the marks: its JavaScript combine pulled page.js, the start scripts and the
* loader into its own deferred file (the matrix on the shared site, 2026-10-08), so the module had no height while
* the page was parsed and a stale copy of the loader. Its own filters keep them out: the delivery's scripts by path,
* our inline scripts by their mark.
*
* @param array $paths Parts of external script URLs SiteGround leaves alone.
* @return array
*/
public static function sgo_external_paths( $paths ) {
$paths = (array) $paths;
$paths[] = (string) wp_parse_url( Config::delivery_url(), PHP_URL_HOST );
return $paths;
}
/**
* SiteGround Speed Optimizer: our inline scripts (the env and the starts) stay where they are, by their mark.
*
* @param array $content Parts of inline scripts SiteGround leaves alone.
* @return array
*/
public static function sgo_inline_content( $content ) {
$content = (array) $content;
$content[] = self::MARK;
return $content;
}
/**
* SiteGround Speed Optimizer: our script handles are neither deferred nor minified by it.
*
* @param array $handles The handles SiteGround leaves alone.
* @return array
*/
public static function sgo_handles( $handles ) {
$ours = array_filter(
array_keys( wp_scripts()->registered ),
static function ( $handle ) {
return 0 === strpos( $handle, 'sranywhere-' );
}
);
return array_merge( (array) $handles, array_values( $ours ) );
}
/**
* WP Rocket (decision 0007): it ignores the marks on our tags. Seen on the shared site on 2026-10-13: it delayed the
* env, the start and the loader until the visitor moves or scrolls (so the module had no height and no engine until
* then), deferred page.js, and kept copies of page.js, the loader and sr7.css in its own cache, which go stale when the
* delivery changes. These filters keep our scripts and sr7.css out of all of that.
*
* Delay JS: its patterns are matched against the whole script tag, so our ids (`sranywhere-…`) and the delivery's host
* catch every one of ours.
*
* @param array $patterns Patterns WP Rocket does not delay.
* @return array
*/
public static function rocket_delay_exclusions( $patterns ) {
$patterns = (array) $patterns;
$patterns[] = 'sranywhere-';
$patterns[] = self::delivery_host();
return $patterns;
}
/**
* WP Rocket: the delivery's scripts are neither minified nor combined into WP Rocket's own files (matched against the
* script's address), and not deferred (page.js has to run before the start scripts).
*
* @param array $hosts Addresses or patterns WP Rocket leaves alone.
* @return array
*/
public static function rocket_external( $hosts ) {
$hosts = (array) $hosts;
$hosts[] = self::delivery_host();
return $hosts;
}
/**
* WP Rocket: our inline scripts (the env and the starts) are not combined, by their mark.
*
* @param array $content Parts of inline scripts WP Rocket leaves alone.
* @return array
*/
public static function rocket_inline( $content ) {
$content = (array) $content;
$content[] = 'sranywhere';
return $content;
}
/**
* WP Rocket: sr7.css is neither minified, nor combined, nor cut down by "Remove Unused CSS" (the engine uses its rules
* for elements it builds later, which that feature would drop). WP Rocket matches these against the stylesheet's host.
*
* @param array $patterns Stylesheets WP Rocket leaves alone.
* @return array
*/
public static function rocket_css( $patterns ) {
$patterns = (array) $patterns;
$patterns[] = self::delivery_host();
return $patterns;
}
/**
* The delivery's host, for the optimizers' lists.
*
* @return string
*/
private static function delivery_host() {
return (string) wp_parse_url( Config::delivery_url(), PHP_URL_HOST );
}
/**
* The elements and attributes a start's markup may contain.
*
* @return array
*/
public static function allowed_html() {
$common = array(
'id' => true,
'class' => true,
);
return array(
'div' => $common,
'sr7-module' => $common + array(
'data-alias' => true,
'data-id' => true,
'data-version' => true,
'data-title' => true,
'data-modal' => true,
'data-sra-start' => true,
),
'sr7-adjuster' => $common,
'sr7-content' => $common,
'sr7-carousel' => $common,
'sr7-slide' => $common + array( 'data-key' => true ),
'sr7-bg' => $common,
'sr7-txt' => $common + array(
'href' => true,
'rel' => true,
'title' => true,
),
'noscript' => array(),
'img' => $common + array(
'src' => true,
'alt' => true,
'title' => true,
'width' => true,
'height' => true,
'loading' => true,
'decoding' => true,
'fetchpriority' => true,
),
'a' => $common + array(
'href' => true,
'rel' => true,
'title' => true,
'target' => true,
),
'span' => $common,
'p' => $common,
'h1' => $common,
'h2' => $common,
'h3' => $common,
'h4' => $common,
);
}
/**
* The start's markup through the allow-list, and the module's scene from the checked value only.
*
* @param array $start A parsed start.
* @return string
*/
private static function markup( array $start ) {
$markup = $start['markup'];
$lists = '';
if ( preg_match( '#]*>(.*?)#s', $markup, $found ) ) {
$markup = str_replace( $found[0], '', $markup );
$lists = self::image_lists( $found[1] );
}
$html = wp_kses( $markup, self::allowed_html() );
if ( ! empty( $start['scene'] ) ) {
$html = (string) preg_replace( '/#', $lists . '', $html, 1 );
}
if ( ! empty( $start['seo'] ) ) {
$slide = self::seo_html( $start['seo'] );
$html = (string) preg_replace_callback(
'#]*)>\s*#',
static function ( $m ) use ( $slide ) {
return '' . $slide . '';
},
$html,
1
);
}
return $html;
}
/**
* The first slide as SR7's Output.php prints it for search engines and pages read without scripts (the contract's
* `seo`): ``, its background only inside `