'320px', 'core/columns' => '320px', 'core/details' => '48px', 'core/template-part' => '400px', 'core/pattern' => '320px', 'core/comments' => '320px', 'core/query' => '480px', 'core/cover' => '400px', 'woocommerce/product-collection' => '420px', 'woocommerce/product-reviews' => '360px', ]; private static array $registered = []; private static int $counter = 0; private static int $nav_counter = 0; private static array $menu_seen = []; private static bool $placeholders = false; private static bool $enqueued = false; public static function boot(): void { add_action( 'init', [ self::class, 'collect_regions' ], 0 ); add_filter( 'register_block_type_args', [ self::class, 'add_support' ], 10, 2 ); add_action( 'init', [ self::class, 'register' ] ); add_action( 'wp_ajax_nopriv_' . self::HEIGHTS_ACTION, [ self::class, 'record_heights' ] ); add_action( 'wp_ajax_' . self::HEIGHTS_ACTION, [ self::class, 'record_heights' ] ); if ( defined( 'WP_CLI' ) && WP_CLI ) { \WP_CLI::add_command( 'aieo heights warm', [ self::class, 'cli_warm' ], [ 'shortdesc' => 'Print a short-lived token whose signed-out height reports count at once.' ] ); \WP_CLI::add_command( 'aieo heights show', [ self::class, 'cli_show' ], [ 'shortdesc' => 'Show the learned placeholder heights of a page.' ] ); } if ( is_admin() ) { return; } add_action( 'shutdown', [ self::class, 'keep_session' ], 0 ); add_filter( 'render_block_data', [ self::class, 'walk' ], 20, 3 ); add_filter( 'render_block', [ self::class, 'late' ], 20, 2 ); add_filter( 'render_block_core/navigation', [ self::class, 'hidden_nav_menu' ], 30, 2 ); add_filter( 'render_block_core/navigation', [ self::class, 'nav_submenus' ], 31, 2 ); add_action( 'send_headers', [ self::class, 'headers' ] ); add_action( 'template_redirect', [ self::class, 'layer_status' ], 0 ); add_action( 'template_redirect', [ self::class, 'in_place_headers' ], 0 ); add_action( 'wp_enqueue_scripts', [ self::class, 'enqueue_for_stubs' ], 999 ); add_filter( 'wp_speculation_rules_href_exclude_paths', [ self::class, 'exclude_layer_from_speculation' ] ); } public static function is_full(): bool { if ( null === self::$full ) { self::$full = 'full' === ( $_GET[ self::QUERY ] ?? '' ); } return self::$full; } public static function keep_session(): void { if ( ! self::is_full() || ! function_exists( 'WC' ) ) { return; } $wc = WC(); if ( is_object( $wc ) && isset( $wc->session ) && is_object( $wc->session ) ) { remove_action( 'shutdown', [ $wc->session, 'save_data' ], 20 ); } } private static ?bool $full = null; private static ?bool $in_place = null; public static function in_place(): bool { if ( null !== self::$in_place ) { return self::$in_place; } if ( ! did_action( 'wp' ) ) { return false; } $listing = function_exists( 'is_shop' ) && ( is_shop() || is_product_taxonomy() || ( is_search() && 'product' === get_query_var( 'post_type' ) ) ); self::$in_place = self::script_fetch() && (bool) apply_filters( 'rooster/defer/in_place', $listing ); return self::$in_place; } private static function script_fetch(): bool { $header = static fn ( string $name ): string => strtolower( trim( (string) ( $_SERVER[ $name ] ?? '' ) ) ); return 'empty' === $header( 'HTTP_SEC_FETCH_DEST' ) && in_array( $header( 'HTTP_SEC_FETCH_MODE' ), [ 'cors', 'same-origin' ], true ) && 'same-origin' === $header( 'HTTP_SEC_FETCH_SITE' ) && 'GET' === strtoupper( (string) ( $_SERVER['REQUEST_METHOD'] ?? 'GET' ) ) && ! self::is_full() && ! isset( $_GET['rooster_part'] ); } public static function renders_whole(): bool { return self::is_full() || self::in_place(); } public static function in_place_headers(): void { if ( ! self::in_place() || headers_sent() ) { return; } header( 'X-Rooster-In-Place: 1' ); header( 'X-Rooster-No-Cache: 1' ); header( 'Vary: Sec-Fetch-Dest', false ); if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } } public static function capture(): void { if ( ! self::is_full() ) { return; } self::$layer_css = isset( $_GET[ self::CSS ] ); unset( $_GET[ self::QUERY ], $_REQUEST[ self::QUERY ], $_GET[ self::CSS ], $_REQUEST[ self::CSS ] ); if ( isset( $_SERVER['REQUEST_URI'] ) ) { $_SERVER['REQUEST_URI'] = remove_query_arg( [ self::QUERY, self::CSS ], (string) $_SERVER['REQUEST_URI'] ); $_SERVER['QUERY_STRING'] = (string) wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY ); } } public static function layer_css(): bool { return self::$layer_css; } public static function enabled(): bool { static $enabled = null; if ( null === $enabled ) { $enabled = (bool) \AIEO_Engine_Settings::get( 'defer', true ) && ! is_admin() && ! wp_is_json_request() && ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ); } return $enabled; } public static function collect_regions(): void { $registrar = new class() { public function register( $name, $args = [] ): void { Defer::add_region( (string) $name, (array) $args ); } }; do_action( 'rooster/defer/register_region', $registrar ); } public static function add_region( string $name, array $args ): void { $height = (string) ( $args['height'] ?? '320px' ); self::$registered[ $name ] = preg_match( '/^[0-9.]+(px|rem|em|vh|dvh|%)$/', $height ) ? $height : '320px'; } public static function add_support( $args, $name ) { $height = self::SUPPORTED[ $name ] ?? self::$registered[ $name ] ?? null; if ( null !== $height && ! isset( $args['supports']['rooster']['defer'] ) ) { $args['supports']['rooster']['defer'] = [ 'allowed' => true, 'height' => $height, 'default' => 'inline' ]; $args['attributes']['roosterDefer'] ??= [ 'type' => 'object' ]; } return $args; } public static function register(): void { \WP_Block_Supports::get_instance()->register( 'rooster-defer', [ 'register_attribute' => static function ( \WP_Block_Type $block_type ): void { if ( empty( $block_type->supports['rooster']['defer']['allowed'] ) ) { return; } if ( ! is_array( $block_type->attributes ) ) { $block_type->attributes = []; } $block_type->attributes['roosterDefer'] ??= [ 'type' => 'object' ]; }, ] ); register_block_type( self::PLACEHOLDER, [ 'api_version' => 3, 'title' => __( 'Deferred region placeholder', 'ai-eshop-optimizer' ), 'attributes' => [ 'id' => [ 'type' => 'string' ], 'height' => [ 'type' => 'string' ], 'label' => [ 'type' => 'string' ], 'trigger' => [ 'type' => 'string' ], 'align' => [ 'type' => 'string' ], ], 'supports' => [ 'inserter' => false, 'html' => false ], 'render_callback' => [ self::class, 'render_placeholder' ], ] ); if ( ! is_admin() ) { $relative = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? 'assets/js/modules/defer.js' : 'assets/js/modules/defer.min.js'; wp_register_script_module( 'rooster-defer', AIEO_ENGINE_URL . '/' . $relative, [ '@wordpress/interactivity', [ 'id' => '@wordpress/interactivity-router', 'import' => 'dynamic' ] ], (string) filemtime( AIEO_ENGINE_DIR . '/' . $relative ) ); } } public static function walk( $parsed_block, $source_block = null, $parent_block = null ) { if ( null !== $parent_block || ! is_array( $parsed_block ) || ! self::enabled() ) { return $parsed_block; } return self::walk_tree( $parsed_block ); } private static function walk_tree( array $block ): array { if ( self::is_deferred( $block ) && empty( $block['attrs']['roosterDefer']['id'] ) ) { $id = self::region_id(); if ( ! self::renders_whole() ) { return self::placeholder_block( $block, $id ); } $block['attrs']['roosterDefer']['id'] = $id; return $block; } if ( ! empty( $block['innerBlocks'] ) ) { foreach ( $block['innerBlocks'] as $i => $inner ) { $block['innerBlocks'][ $i ] = self::walk_tree( $inner ); } } return $block; } private static function placeholder_block( array $block, string $id ): array { return [ 'blockName' => self::PLACEHOLDER, 'attrs' => [ 'id' => $id, 'height' => self::placeholder_height( $block, $id ), 'label' => self::label( $block ), 'trigger' => (string) ( $block['attrs']['roosterDefer']['trigger'] ?? 'layer' ), 'align' => (string) ( $block['attrs']['align'] ?? '' ), ], 'innerBlocks' => [], 'innerHTML' => '', 'innerContent' => [], ]; } private static function is_deferred( array $block ): bool { $defer = $block['attrs']['roosterDefer'] ?? null; if ( ! is_array( $defer ) || empty( $defer['enabled'] ) || self::PLACEHOLDER === ( $block['blockName'] ?? '' ) ) { return false; } if ( function_exists( 'is_cart' ) && ( is_cart() || is_checkout() ) && '0px' !== self::height( $block ) && 'intent' !== ( $defer['trigger'] ?? '' ) ) { return false; } if ( ! is_user_logged_in() && in_array( (string) ( $block['blockName'] ?? '' ), (array) apply_filters( 'rooster/defer/in_place_for_guests', [ 'rooster-lms/lesson-qa' ] ), true ) ) { return false; } return (bool) apply_filters( 'rooster/defer/is_deferred', true, $block ); } private static function region_id(): string { ++self::$counter; return 'rooster-defer-' . self::$counter; } private static function height( array $block ): string { $h = (string) ( $block['attrs']['roosterDefer']['height'] ?? '' ); if ( '' === $h ) { $type = \WP_Block_Type_Registry::get_instance()->get_registered( (string) ( $block['blockName'] ?? '' ) ); $h = (string) ( $type->supports['rooster']['defer']['height'] ?? '320px' ); } return preg_match( '/^[0-9.]+(px|rem|em|vh|dvh|%)$/', $h ) ? $h : '320px'; } private static function placeholder_height( array $block, string $id ): string { $learned = self::learned_height( $id ); return null !== $learned ? $learned . 'px' : self::height( $block ); } public static function learned_height( string $id ): ?int { if ( null === self::$learned ) { $row = get_transient( self::heights_key( self::heights_path( 'http://x' . (string) ( $_SERVER['REQUEST_URI'] ?? '/' ) ), DeviceClass::current() ) ); self::$learned = is_array( $row ) && is_array( $row['value'] ?? null ) ? $row['value'] : []; } return isset( self::$learned[ $id ] ) ? (int) self::$learned[ $id ] : null; } private static function heights_path( string $url ): string { $path = (string) wp_parse_url( $url, PHP_URL_PATH ); $query = (string) wp_parse_url( $url, PHP_URL_QUERY ); parse_str( $query, $args ); foreach ( array_keys( (array) $args ) as $key ) { if ( self::QUERY === $key || str_starts_with( (string) $key, 'utm_' ) || in_array( $key, [ 'fbclid', 'gclid', 'msclkid', 'srsltid', 'ttclid' ], true ) ) { unset( $args[ $key ] ); } } ksort( $args ); return ( '' !== $path ? $path : '/' ) . ( $args ? '?' . http_build_query( $args ) : '' ); } private static function heights_key( string $path, string $device ): string { return 'rooster_dh_' . md5( $path . '|' . $device ); } public static function record_heights(): void { if ( ! check_ajax_referer( self::HEIGHTS_ACTION, 'nonce', false ) ) { wp_send_json_error( null, 403 ); } if ( is_user_logged_in() ) { wp_send_json_error( null, 403 ); } $warm = isset( $_COOKIE[ self::WARM_COOKIE ] ) ? (string) $_COOKIE[ self::WARM_COOKIE ] : ''; $trusted = '' !== $warm && false !== get_transient( 'rooster_dh_warm_' . hash( 'sha256', $warm ) ); $addr = hash_hmac( 'sha256', (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ), wp_salt( 'nonce' ) ); $rate = 'rooster_dh_rate_' . substr( $addr, 0, 20 ); $sent = (int) get_transient( $rate ); if ( ! $trusted && $sent >= self::HEIGHTS_RATE ) { wp_send_json_error( null, 429 ); } set_transient( $rate, $sent + 1, HOUR_IN_SECONDS ); $raw = wp_unslash( $_POST ); $path = is_string( $raw['path'] ?? null ) ? (string) $raw['path'] : ''; $device = in_array( $raw['device'] ?? '', [ 'mobile', 'desktop' ], true ) ? (string) $raw['device'] : ''; $sizes = json_decode( is_string( $raw['h'] ?? null ) ? (string) $raw['h'] : '', true ); if ( '' === $device || ! str_starts_with( $path, '/' ) || str_starts_with( $path, '//' ) || strlen( $path ) > 300 || ! is_array( $sizes ) || count( $sizes ) > 20 ) { wp_send_json_error( null, 400 ); } $key = self::heights_key( self::heights_path( 'http://x' . $path ), $device ); $row = get_transient( $key ); $row = is_array( $row ) ? $row : [ 'value' => [], 'reports' => [] ]; $was = $row; $now = time(); foreach ( $sizes as $id => $h ) { if ( ! preg_match( '/^(rooster-defer-\d{1,3}|aieo-mount-[a-z0-9_]{1,40})$/', (string) $id ) || ! is_numeric( $h ) ) { continue; } $h = (int) round( (float) $h ); if ( $h < 0 || $h > self::HEIGHTS_MAX ) { continue; } if ( $trusted ) { $row['value'][ $id ] = $h; $row['reports'][ $id ] = []; continue; } $reports = array_values( array_filter( (array) ( $row['reports'][ $id ] ?? [] ), static fn ( $r ) => is_array( $r ) && $now - (int) ( $r[2] ?? 0 ) < WEEK_IN_SECONDS && ( $r[1] ?? '' ) !== $addr ) ); $reports[] = [ $h, $addr, $now ]; $reports = array_slice( $reports, -8 ); $row['reports'][ $id ] = $reports; $agree = array_values( array_filter( $reports, static fn ( $r ) => abs( (int) $r[0] - $h ) <= self::HEIGHTS_AGREE ) ); if ( count( $agree ) >= 2 ) { $heights = array_map( static fn ( $r ) => (int) $r[0], $agree ); sort( $heights ); $row['value'][ $id ] = $heights[ intdiv( count( $heights ), 2 ) ]; } } if ( $row !== $was ) { set_transient( $key, $row, 30 * DAY_IN_SECONDS ); } if ( $trusted ) { self::warm_log( 'report', self::heights_path( 'http://x' . $path ), $device, count( $sizes ) ); } wp_send_json_success(); } private static function warm_log( string $event, string $path, string $device, int $count ): void { $log = (array) get_option( 'rooster_dh_warm_log', [] ); $log[] = [ gmdate( 'c' ), $event, $path, $device, $count ]; update_option( 'rooster_dh_warm_log', array_slice( $log, -200 ), false ); } public static function cli_warm( $args, $assoc ): void { $minutes = max( 1, min( 60, (int) ( $assoc['minutes'] ?? 15 ) ) ); $token = wp_generate_password( 32, false ); set_transient( 'rooster_dh_warm_' . hash( 'sha256', $token ), time() + $minutes * MINUTE_IN_SECONDS, $minutes * MINUTE_IN_SECONDS ); self::warm_log( 'minted', '', '', $minutes ); \WP_CLI::line( $token ); \WP_CLI::success( sprintf( 'Send it as the cookie %s from a signed-out browser: its height reports count at once for %d minutes.', self::WARM_COOKIE, $minutes ) ); } public static function cli_show( $args, $assoc ): void { $path = self::heights_path( 'http://x' . (string) ( $args[0] ?? '/' ) ); $device = in_array( $assoc['device'] ?? '', [ 'mobile', 'desktop' ], true ) ? (string) $assoc['device'] : 'desktop'; $row = get_transient( self::heights_key( $path, $device ) ); \WP_CLI::line( $path . ' (' . $device . '): ' . wp_json_encode( is_array( $row ) ? [ 'value' => $row['value'] ?? [], 'reports' => array_map( 'count', (array) ( $row['reports'] ?? [] ) ) ] : null ) ); } private static function label( array $block ): string { return (string) ( $block['attrs']['roosterDefer']['label'] ?? $block['attrs']['metadata']['name'] ?? __( 'More content', 'ai-eshop-optimizer' ) ); } public static function render_placeholder( $attributes ): string { $id = (string) ( $attributes['id'] ?? self::region_id() ); if ( ! self::$placeholders ) { self::$placeholders = true; self::enqueue(); } $label = (string) ( $attributes['label'] ?? __( 'More content', 'ai-eshop-optimizer' ) ); return sprintf( '
', esc_attr( $id ), esc_attr( (string) ( $attributes['trigger'] ?? 'layer' ) ), esc_attr( $label ), esc_attr( (string) ( $attributes['height'] ?? '320px' ) ), esc_url( self::layer_url() . '#' . $id ), esc_html( sprintf( __( 'Show %s', 'ai-eshop-optimizer' ), $label ) ), esc_attr( self::align_class( (array) $attributes ) ), null !== self::learned_height( $id ) ? ' data-rooster-learned' : '' ); } public static function late( $html, $block ): string { if ( ! self::enabled() || ! self::is_deferred( $block ) ) { return (string) $html; } $id = (string) ( $block['attrs']['roosterDefer']['id'] ?? '' ); if ( '' === $id ) { $id = self::region_id(); if ( ! self::renders_whole() ) { return self::render_placeholder( [ 'id' => $id, 'height' => self::placeholder_height( $block, $id ), 'label' => self::label( $block ), 'trigger' => (string) ( $block['attrs']['roosterDefer']['trigger'] ?? 'layer' ), 'align' => (string) ( $block['attrs']['align'] ?? '' ) ] ); } } if ( ! self::renders_whole() ) { return (string) $html; } if ( ! self::is_full() ) { self::enqueue(); } if ( str_contains( (string) $html, 'data-wp-router-region="' . esc_attr( $id ) . '"' ) ) { return (string) $html; } return sprintf( '
%2$s
', esc_attr( $id ), (string) $html, esc_attr( self::align_class( (array) ( $block['attrs'] ?? [] ) ) ) ); } public static function hidden_nav_menu( $html, $block ): string { $html = (string) $html; if ( ! self::enabled() || 'desktop' !== DeviceClass::current() ) { return $html; } $tags = new \WP_HTML_Tag_Processor( $html ); if ( ! $tags->next_tag( 'nav' ) || ! $tags->has_class( 'wp-block-hidden-desktop' ) ) { return $html; } if ( str_contains( $html, 'disable-default-overlay' ) && str_contains( $html, 'wp-block-navigation__overlay-container' ) ) { return $html; } $content = strpos( $html, 'wp-block-navigation__responsive-container-content' ); $start = false === $content ? false : strpos( $html, '', $m[0][1] ) + 1; break; } } if ( false === $end ) { return $html; } $id = 'rooster-defer-nav-' . ( ++self::$nav_counter ); $list = substr( $html, $start, $end - $start ); if ( self::renders_whole() ) { $region = sprintf( '
%2$s
', esc_attr( $id ), $list ); } else { $region = self::render_placeholder( [ 'id' => $id, 'height' => '0px', 'label' => __( 'Menu', 'ai-eshop-optimizer' ), 'trigger' => 'intent' ] ); } return substr( $html, 0, $start ) . $region . substr( $html, $end ); } public static function nav_submenus( $html, $block ): string { $html = (string) $html; if ( ! self::enabled() || ! DeviceClass::enabled() || 'desktop' !== DeviceClass::current() || ! str_contains( $html, 'wp-block-navigation__submenu-container' ) || str_contains( $html, 'open-always' ) ) { return $html; } $tags = new \WP_HTML_Tag_Processor( $html ); if ( ! $tags->next_tag( 'nav' ) || $tags->has_class( 'wp-block-hidden-desktop' ) ) { return $html; } $full = self::renders_whole(); $out = ''; $at = 0; $pos = 0; $depth = 0; $cut = 0; while ( preg_match( '#<(/?)ul\b[^>]*>#i', $html, $m, PREG_OFFSET_CAPTURE, $pos ) ) { $tag = $m[0][0]; $start = $m[0][1]; $pos = $start + strlen( $tag ); if ( '/' === $m[1][0] ) { --$depth; continue; } if ( 2 !== ++$depth || ! str_contains( $tag, 'wp-block-navigation__submenu-container' ) ) { continue; } $inner = 1; $scan = $pos; $close = false; while ( preg_match( '#<(/?)ul\b[^>]*>#i', $html, $n, PREG_OFFSET_CAPTURE, $scan ) ) { $scan = $n[0][1] + strlen( $n[0][0] ); $inner += '/' === $n[1][0] ? -1 : 1; if ( 0 === $inner ) { $close = $n[0][1]; break; } } if ( false === $close ) { return $html; } $open = new \WP_HTML_Tag_Processor( $tag ); if ( ! $open->next_tag( 'ul' ) || null !== $open->get_attribute( 'id' ) ) { continue; } $basis = (string) preg_replace( '/\s+aria-current="[^"]*"|\s*\bcurrent[-_](?:menu|page)[-_](?:item|parent|ancestor)\b/', '', substr( $html, $pos, $close - $pos ) ); $key = substr( md5( $basis ), 0, 10 ); $nth = self::$menu_seen[ $key ] = ( self::$menu_seen[ $key ] ?? 0 ) + 1; $id = 'rooster-defer-menu-' . $key . ( $nth > 1 ? '-' . $nth : '' ); $item = strrpos( substr( $html, 0, $start ), ']*\bhref="([^"]*)"/', substr( $html, $item, $start - $item ), $link ) ? html_entity_decode( $link[1], ENT_QUOTES ) : ''; $open->add_class( 'rooster-defer' ); if ( $full ) { $open->add_class( 'is-loaded' ); } $open->set_attribute( 'id', $id ); $open->set_attribute( 'data-wp-router-region', $id ); if ( null === $open->get_attribute( 'data-wp-interactive' ) ) { $open->set_attribute( 'data-wp-interactive', 'core/navigation' ); } $open->set_attribute( 'data-rooster-defer', 'menu' ); if ( '' !== $of ) { $open->set_attribute( 'data-rooster-menu-of', $of ); } $out .= substr( $html, $at, $start - $at ) . $open->get_updated_html() . ( $full ? substr( $html, $pos, $close - $pos ) : '' ); $at = $close; $pos = $close; ++$cut; } if ( ! $cut ) { return $html; } if ( ! $full ) { self::enqueue(); } return $out . substr( $html, $at ); } public static function headers(): void { if ( self::is_full() && ! headers_sent() ) { header( 'X-Robots-Tag: noindex, nofollow', true ); } } public static function layer_status(): void { if ( self::is_full() && is_404() && ! headers_sent() ) { status_header( 200 ); } } private static function align_class( array $attributes ): string { $align = (string) ( $attributes['align'] ?? '' ); return in_array( $align, [ 'full', 'wide' ], true ) ? ' align' . $align : ''; } public static function enqueue_for_stubs(): void { if ( self::$enqueued ) { return; } if ( Performance::registered() || Performance::delays_any() ) { self::enqueue(); return; } foreach ( array_keys( Performance::handles() ) as $handle ) { if ( wp_script_is( $handle, 'enqueued' ) ) { self::enqueue(); return; } } } public static function later_image( string $html ): string { $tags = new \WP_HTML_Tag_Processor( $html ); if ( ! $tags->next_tag( 'img' ) ) { return $html; } $src = (string) $tags->get_attribute( 'src' ); if ( '' === $src || str_starts_with( $src, 'data:' ) ) { return $html; } $width = max( 1, (int) $tags->get_attribute( 'width' ) ); $height = max( 1, (int) $tags->get_attribute( 'height' ) ); $tags->set_attribute( 'data-rooster-later', true ); $tags->set_attribute( 'data-rooster-later-src', $src ); $tags->remove_attribute( 'src' ); foreach ( [ 'srcset', 'sizes' ] as $name ) { $value = $tags->get_attribute( $name ); if ( is_string( $value ) && '' !== $value ) { $tags->set_attribute( 'data-rooster-later-' . $name, $value ); $tags->remove_attribute( $name ); } } self::enqueue(); $blank = 'data:image/svg+xml,' . rawurlencode( sprintf( '', $width, $height ) ); return (string) preg_replace( '/^(\s*get_updated_html(), 1 ); } public static function enqueue(): void { if ( self::$enqueued ) { return; } self::$enqueued = true; wp_enqueue_script_module( 'rooster-defer' ); $state = [ 'fullUrl' => self::layer_url(), 'human' => false, 'loaded' => false, ]; $heights = self::heights_config(); if ( $heights ) { $state['heights'] = $heights; } wp_interactivity_state( 'rooster/defer', $state ); } public static function heights_config(): array { if ( is_user_logged_in() ) { return []; } return [ 'url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::HEIGHTS_ACTION ), 'device' => DeviceClass::current(), ]; } public static function exclude_layer_from_speculation( $paths ): array { $paths = (array) $paths; $paths[] = '/*\\?*' . self::QUERY . '=*'; return $paths; } public static function current_url(): string { $uri = (string) ( $_SERVER['REQUEST_URI'] ?? '/' ); return remove_query_arg( self::QUERY, home_url( $uri ) ); } public static function layer_url(): string { $url = self::current_url(); $args = array_map( 'strval', (array) apply_filters( 'rooster/defer/action_args', self::ACTION_ARGS ) ); parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $present ); foreach ( array_keys( (array) $present ) as $key ) { if ( str_starts_with( (string) $key, 'attribute_' ) ) { $args[] = (string) $key; } } $args[] = self::CSS; $layer = [ self::QUERY => 'full' ]; if ( class_exists( UsedCss::class ) && UsedCss::active() ) { $layer[ self::CSS ] = '1'; } return add_query_arg( $layer, remove_query_arg( $args, $url ) ); } }