` 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 `