registered[ $handle ] ) ) { return; } $seen[ $handle ] = true; $script = $scripts->registered[ $handle ]; foreach ( (array) $script->deps as $dep ) { $walk( (string) $dep ); } if ( empty( $script->extra['strategy'] ) && ! empty( $script->src ) ) { $scripts->add_data( $handle, 'strategy', 'defer' ); } }; foreach ( (array) $scripts->queue as $handle ) { $walk( (string) $handle ); } $before = $scripts->registered['jquery-ui-core']->extra['before'] ?? null; if ( is_array( $before ) && 'defer' === $scripts->get_data( 'jquery-core', 'strategy' ) ) { $scripts->registered['jquery-ui-core']->extra['before'] = array_map( static fn( $code ) => is_string( $code ) && 'jQuery.uiBackCompat = true;' === trim( $code ) ? self::UI_BACK_COMPAT : $code, $before ); } } private static array $lcp = []; public static function note_lcp_image( $loading_attrs, $tag_name, $attr ): array { $loading_attrs = (array) $loading_attrs; if ( 'img' === $tag_name && 'high' === ( $loading_attrs['fetchpriority'] ?? '' ) && is_array( $attr ) ) { self::lcp_image( (string) ( $attr['src'] ?? '' ), (string) ( $attr['srcset'] ?? '' ), (string) ( $attr['sizes'] ?? '' ) ); } return $loading_attrs; } public static function note_content_image( $image ): string { $image = (string) $image; $tags = new \WP_HTML_Tag_Processor( $image ); if ( $tags->next_tag( 'img' ) && 'high' === $tags->get_attribute( 'fetchpriority' ) ) { self::lcp_image( (string) $tags->get_attribute( 'src' ), (string) $tags->get_attribute( 'srcset' ), (string) $tags->get_attribute( 'sizes' ) ); } return $image; } public static function lcp_image( string $src, string $srcset = '', string $sizes = '', bool $replace = false ): void { if ( '' === $src ) { return; } $sizes = (string) preg_replace( '/^\s*auto\s*,\s*/', '', $sizes ); if ( [] === self::$lcp || $replace ) { self::$lcp = [ 'src' => $src, 'srcset' => $srcset, 'sizes' => $sizes ]; } elseif ( $src === self::$lcp['src'] && '' === self::$lcp['srcset'] && '' !== $srcset ) { self::$lcp['srcset'] = $srcset; self::$lcp['sizes'] = $sizes; } } public static function small_candidate( string $srcset, int $width ): string { $best = [ '', PHP_INT_MAX ]; if ( preg_match_all( '/(\S+)\s+(\d+)w/', $srcset, $m, PREG_SET_ORDER ) ) { foreach ( $m as [ , $url, $w ] ) { if ( (int) $w >= $width && (int) $w < $best[1] ) { $best = [ $url, (int) $w ]; } } } return $best[0]; } private static array $lcp_picture = []; public static function lcp_picture( array $sources ): void { if ( [] !== self::$lcp_picture || [] !== self::$lcp ) { return; } foreach ( $sources as $source ) { if ( is_array( $source ) && ! empty( $source['href'] ) ) { self::$lcp_picture[] = [ 'media' => (string) ( $source['media'] ?? '' ), 'href' => (string) $source['href'], 'srcset' => (string) ( $source['srcset'] ?? '' ), 'sizes' => (string) ( $source['sizes'] ?? '' ) ]; } } } public static function has_lcp(): bool { return [] !== self::$lcp || [] !== self::$lcp_picture; } public static function reset_lcp_image(): void { self::$lcp = []; self::$lcp_picture = []; } public static function preload_lcp_image( $resources ): array { $resources = (array) $resources; if ( is_admin() ) { return $resources; } if ( [] !== self::$lcp_picture ) { foreach ( self::$lcp_picture as $source ) { $resource = [ 'href' => $source['href'], 'as' => 'image', 'fetchpriority' => 'high' ]; if ( '' !== $source['media'] ) { $resource['media'] = $source['media']; } if ( '' !== $source['srcset'] ) { $resource['imagesrcset'] = $source['srcset']; if ( '' !== $source['sizes'] ) { $resource['imagesizes'] = $source['sizes']; } } $resources[] = $resource; } return $resources; } if ( [] === self::$lcp ) { return $resources; } $resource = [ 'href' => self::$lcp['src'], 'as' => 'image', 'fetchpriority' => 'high' ]; if ( '' !== self::$lcp['srcset'] ) { $resource['imagesrcset'] = self::$lcp['srcset']; if ( '' !== self::$lcp['sizes'] ) { $resource['imagesizes'] = self::$lcp['sizes']; } } $resources[] = $resource; return $resources; } public static function low_priority_modules(): void { if ( ! \AIEO_Engine_Settings::get( 'defer_scripts', true ) || ! function_exists( 'wp_script_modules' ) ) { return; } $modules = wp_script_modules(); $seen = []; $walk = static function ( string $id ) use ( $modules, &$seen, &$walk ): void { if ( isset( $seen[ $id ] ) ) { return; } $seen[ $id ] = true; $module = $modules->get_registered( $id ); if ( ! $module ) { return; } foreach ( (array) ( $module['dependencies'] ?? [] ) as $dep ) { $walk( (string) ( is_array( $dep ) ? ( $dep['id'] ?? '' ) : $dep ) ); } if ( 'high' !== ( $module['fetchpriority'] ?? 'auto' ) ) { $modules->set_fetchpriority( $id, (string) apply_filters( 'rooster/performance/module_priority', 'low', $id ) ); } }; foreach ( $modules->get_queue() as $id ) { $walk( (string) $id ); } } public static function delay_modules(): bool { if ( null === self::$modules_on ) { $on = (bool) \AIEO_Engine_Settings::get( 'delay_modules', true ) && 'wp-login.php' !== ( $GLOBALS['pagenow'] ?? '' ) && ! is_customize_preview() && ! ( function_exists( 'is_checkout' ) && is_checkout() ); self::$modules_on = (bool) apply_filters( 'rooster/performance/delay_modules', $on ); } return self::$modules_on; } private static function module_stub( array $attributes ): array { $id = (string) ( $attributes['id'] ?? '' ); if ( empty( $attributes['src'] ) || ! str_ends_with( $id, '-js-module' ) || ! self::delay_modules() ) { return $attributes; } self::$module_stubs = true; $attributes['type'] = 'text/plain'; $attributes['data-rooster-module'] = substr( $id, 0, -strlen( '-js-module' ) ); return $attributes; } public static function hold_module_preloads(): void { if ( ! function_exists( 'wp_script_modules' ) || ! self::delay_modules() ) { return; } $modules = wp_script_modules(); foreach ( [ 'wp_head', 'wp_footer' ] as $hook ) { $priority = has_action( $hook, [ $modules, 'print_script_module_preloads' ] ); if ( false === $priority ) { continue; } remove_action( $hook, [ $modules, 'print_script_module_preloads' ], $priority ); add_action( $hook, static function () use ( $modules ): void { ob_start(); $modules->print_script_module_preloads(); $tags = new \WP_HTML_Tag_Processor( (string) ob_get_clean() ); while ( $tags->next_tag( 'link' ) ) { $href = (string) $tags->get_attribute( 'href' ); if ( '' !== $href ) { self::$preloads[] = $href; } } }, $priority ); } } public static function print_module_gate(): void { if ( ! self::$module_stubs ) { return; } if ( self::$preloads ) { wp_print_inline_script_tag( (string) wp_json_encode( array_values( array_unique( self::$preloads ) ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG ), [ 'type' => 'application/json', 'id' => 'rooster-module-preloads' ] ); } $file = AIEO_ENGINE_DIR . ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '/assets/js/inline/module-gate.js' : '/assets/js/inline/module-gate.min.js' ); wp_print_inline_script_tag( (string) file_get_contents( $file ), [ 'id' => 'rooster-module-gate' ] ); } public static function delay_scripts(): bool { if ( null === self::$delay_on ) { $on = (bool) \AIEO_Engine_Settings::get( 'delay_scripts', true ) && 'wp-login.php' !== ( $GLOBALS['pagenow'] ?? '' ) && ! is_customize_preview() && ! self::elementor_preview() && ! ( function_exists( 'is_checkout' ) && is_checkout() ) && ! ( function_exists( 'get_rocket_option' ) && get_rocket_option( 'delay_js', 0 ) ); self::$delay_on = (bool) apply_filters( 'rooster/performance/delay_scripts', $on ); } return self::$delay_on; } private static function elementor_preview(): bool { return class_exists( '\Elementor\Plugin', false ) && isset( \Elementor\Plugin::$instance->preview ) && is_object( \Elementor\Plugin::$instance->preview ) && \Elementor\Plugin::$instance->preview->is_preview_mode(); } private static function undeclared_deps( string $handle ): array { $src = (string) ( wp_scripts()->registered[ $handle ]->src ?? '' ); $deps = str_contains( $src, '/woocommerce-paypal-payments/' ) ? [ 'jquery' ] : []; return (array) apply_filters( 'rooster/performance/undeclared_deps', $deps, $handle, $src ); } private static function keep(): array { $scripts = wp_scripts(); $queue = (array) $scripts->queue; if ( null !== self::$keep && count( $queue ) === self::$keep_queue ) { return self::$keep; } $roots = [ 'admin-bar', 'aieo-captcha', 'aieo-survey', 'aieo-controls', 'aieo-contact-form', 'aieo-product-request' ]; if ( self::prints_module_translations() ) { array_push( $roots, 'wp-i18n', 'wp-hooks' ); } $roots = (array) apply_filters( 'rooster/performance/delay_keep', $roots ); $renders = apply_filters( 'rooster/performance/delay_keep_renderers', true ) ? [ 'react' => true, 'react-dom' => true, 'wp-element' => true ] : []; $memo = []; $renderer = static function ( string $handle ) use ( $scripts, $renders, &$memo, &$renderer ): bool { if ( ! isset( $memo[ $handle ] ) ) { $memo[ $handle ] = isset( $renders[ $handle ] ); foreach ( (array) ( $scripts->registered[ $handle ]->deps ?? [] ) as $dep ) { if ( ! $memo[ $handle ] && $renderer( (string) $dep ) ) { $memo[ $handle ] = true; } } } return $memo[ $handle ]; }; $keep = []; $add = static function ( string $handle ) use ( $scripts, &$keep, &$add ): void { if ( isset( $keep[ $handle ] ) ) { return; } $keep[ $handle ] = true; foreach ( array_merge( (array) ( $scripts->registered[ $handle ]->deps ?? [] ), self::undeclared_deps( $handle ) ) as $dep ) { $add( (string) $dep ); } }; foreach ( $roots as $handle ) { $add( (string) $handle ); } foreach ( $queue as $handle ) { if ( $renderer( (string) $handle ) ) { $add( (string) $handle ); } } self::$keep_queue = count( $queue ); return self::$keep = $keep; } public static function prints_module_translations(): bool { if ( ! function_exists( 'load_script_module_textdomain' ) || ! function_exists( 'wp_script_modules' ) ) { return false; } $modules = wp_script_modules(); $queue = (array) $modules->get_queue(); if ( null !== self::$translates && count( $queue ) === self::$translates_queue ) { return self::$translates; } $seen = []; $walk = static function ( string $id ) use ( $modules, &$seen, &$walk ): bool { if ( '' === $id || isset( $seen[ $id ] ) ) { return false; } $seen[ $id ] = true; $module = $modules->get_registered( $id ); if ( ! is_array( $module ) ) { return false; } if ( load_script_module_textdomain( $id, (string) ( $module['textdomain'] ?? 'default' ), (string) ( $module['translations_path'] ?? '' ) ) ) { return true; } foreach ( (array) ( $module['dependencies'] ?? [] ) as $dep ) { if ( $walk( (string) ( is_array( $dep ) ? ( $dep['id'] ?? '' ) : $dep ) ) ) { return true; } } return false; }; $prints = false; foreach ( $queue as $id ) { if ( $walk( (string) $id ) ) { $prints = true; break; } } self::$translates_queue = count( $queue ); return self::$translates = $prints; } public static function is_delayed( string $handle ): bool { if ( isset( self::$delayed[ $handle ] ) ) { return self::$delayed[ $handle ]; } $script = wp_scripts()->registered[ $handle ] ?? null; self::$delayed[ $handle ] = false; if ( ! $script || isset( self::handles()[ $handle ] ) || ! self::delay_scripts() || ! in_array( 'rooster-defer', wp_script_modules()->get_queue(), true ) ) { return false; } $waits = false; foreach ( (array) $script->deps as $dep ) { if ( self::is_delayed( (string) $dep ) ) { $waits = true; break; } } if ( ! $waits && ! empty( $script->src ) ) { $waits = ! isset( self::keep()[ $handle ] ); } return self::$delayed[ $handle ] = $waits; } public static function delays_any(): bool { if ( ! self::delay_scripts() ) { return false; } $scripts = wp_scripts(); $keep = self::keep(); foreach ( (array) $scripts->queue as $handle ) { $script = $scripts->registered[ $handle ] ?? null; if ( $script && ( ! empty( $script->src ) || $script->deps ) && ! isset( $keep[ $handle ] ) && ! isset( self::handles()[ $handle ] ) ) { return true; } } return false; } private static function delay_attributes( array $attributes, string $id, string $handle ): array { if ( ! preg_match( '/-js(-(before|after|translations))?$/', $id ) || 'module' === ( $attributes['type'] ?? '' ) ) { return $attributes; } if ( in_array( $id, (array) apply_filters( 'rooster/performance/delay_inline_at_load', [ 'wc-settings-js-before' ] ), true ) || ! self::is_delayed( $handle ) ) { return $attributes; } self::$stubs = true; $attributes['type'] = 'text/plain'; $attributes['data-rooster-delay'] = $handle; unset( $attributes['defer'], $attributes['async'] ); return $attributes; } public static function wrap_delayed_callbacks(): void { global $wp_filter; if ( ! self::delay_scripts() || ! in_array( 'rooster-defer', wp_script_modules()->get_queue(), true ) ) { return; } $targets = (array) apply_filters( 'rooster/performance/delay_callbacks', [ [ 'wp_footer', 'AI_eShop_Chat', 'render_floating_widget' ], [ 'wp_footer', '', 'om_debounce_shipping_bar_js' ], ] ); foreach ( $targets as $target ) { [ $hook, $class, $method ] = array_map( 'strval', array_pad( (array) $target, 3, '' ) ); if ( empty( $wp_filter[ $hook ] ) ) { continue; } foreach ( $wp_filter[ $hook ]->callbacks as $priority => $callbacks ) { foreach ( $callbacks as $callback ) { $fn = $callback['function']; $matches = '' === $class ? is_string( $fn ) && $fn === $method : is_array( $fn ) && ( $fn[1] ?? '' ) === $method && ( is_object( $fn[0] ) ? $fn[0] instanceof $class : $fn[0] === $class ); if ( ! $matches ) { continue; } $handle = sanitize_key( '' === $class ? $method : $class ); remove_action( $hook, $fn, $priority ); add_action( $hook, static function () use ( $fn, $handle ): void { ob_start(); call_user_func( $fn ); echo self::stub_scripts( (string) ob_get_clean(), $handle ); }, $priority ); } } } } public static function stub_scripts( string $html, string $handle ): string { if ( ! str_contains( $html, 'next_tag( 'script' ) ) { $type = strtolower( trim( (string) $tags->get_attribute( 'type' ) ) ); if ( '' !== $type && ! in_array( $type, [ 'text/javascript', 'application/javascript' ], true ) ) { continue; } $tags->set_attribute( 'type', 'text/plain' ); $tags->set_attribute( 'data-rooster-delay', $handle ); $tags->remove_attribute( 'defer' ); $tags->remove_attribute( 'async' ); } self::$stubs = true; return $tags->get_updated_html(); } private static array $prefetch = []; public static function gate_script_prefetch( $urls, $relation ) { if ( 'prefetch' !== $relation || ! \AIEO_Engine_Settings::get( 'delay_scripts', true ) ) { return $urls; } $keep = []; foreach ( (array) $urls as $url ) { $href = is_array( $url ) ? (string) ( $url['href'] ?? '' ) : (string) $url; if ( '' !== $href && preg_match( '/\.m?js(\?|$)/', $href ) ) { self::$prefetch[] = $href; continue; } $keep[] = $url; } return $keep; } public static function print_script_prefetch(): void { if ( ! self::$prefetch ) { return; } wp_print_inline_script_tag( (string) wp_json_encode( array_values( array_unique( self::$prefetch ) ), JSON_UNESCAPED_SLASHES ), [ 'type' => 'application/json', 'id' => 'rooster-prefetch' ] ); } public static function delayed(): array { return array_keys( array_filter( self::$delayed ) ); } public static function reset(): void { self::$delayed = []; self::$keep = null; self::$keep_queue = -1; self::$translates = null; self::$translates_queue = -1; self::$delay_on = null; self::$modules_on = null; self::$module_stubs = false; self::$preloads = []; self::$handles = null; self::$handles_done = false; self::$stubs = false; self::$accordion_state = false; self::$social_services = []; self::$withheld = []; self::$rendered = []; self::$restored = []; } public static function accordion_hidden_state( $block ) { if ( ! self::$accordion_state && 'core/accordion-item' === ( $block['blockName'] ?? '' ) ) { self::$accordion_state = true; wp_interactivity_state( 'core/accordion', [ 'isHidden' => static function () { $context = wp_interactivity_get_context(); return empty( $context['openByDefault'] ) ? 'until-found' : null; }, ] ); } return (array) $block; } private const PRODUCT_FIELDS = [ 'id', 'type', 'parent', 'variation', 'permalink', 'is_purchasable', 'is_in_stock', 'has_options', 'sold_individually', 'add_to_cart', 'attributes' ]; private const PRODUCT_STATE_SAFE = [ 'woocommerce/product-button', 'woocommerce/product-collection', '@woocommerce/stores/woocommerce/cart', '@woocommerce/stores/woocommerce/products' ]; private const PRODUCT_STATE_READERS = [ 'woocommerce/product-elements', 'woocommerce/product-gallery', 'woocommerce/product-sale-badge', 'woocommerce/add-to-wishlist-button', 'woocommerce/add-to-cart-with-options', 'woocommerce/add-to-cart-form' ]; public static function trim_products_state( $data ) { $products = is_array( $data ) ? ( $data['state']['woocommerce/products']['products'] ?? null ) : null; if ( ! is_array( $products ) || ! $products || ! \AIEO_Engine_Settings::get( 'trim_products_state', true ) || Defer::is_full() || is_singular( 'product' ) || ! empty( $data['state']['woocommerce/products']['productId'] ) || self::products_store_readers() ) { return $data; } $keep = array_flip( array_map( 'strval', (array) apply_filters( 'rooster/performance/products_state_fields', self::PRODUCT_FIELDS ) ) ); foreach ( $products as $id => $product ) { if ( ! is_array( $product ) ) { continue; } $slim = array_intersect_key( $product, $keep ); if ( isset( $slim['attributes'] ) && is_array( $slim['attributes'] ) ) { $slim['attributes'] = array_map( static function ( $attribute ) { $attribute = is_object( $attribute ) ? get_object_vars( $attribute ) : $attribute; if ( ! is_array( $attribute ) ) { return $attribute; } $terms = []; foreach ( (array) ( $attribute['terms'] ?? [] ) as $term ) { $term = is_object( $term ) ? get_object_vars( $term ) : $term; $terms[] = is_array( $term ) ? array_intersect_key( $term, [ 'name' => true, 'slug' => true ] ) : $term; } return [ 'name' => $attribute['name'] ?? '', 'taxonomy' => $attribute['taxonomy'] ?? '', 'terms' => $terms ]; }, $slim['attributes'] ); } $data['state']['woocommerce/products']['products'][ $id ] = $slim; } return $data; } private static function products_store_readers(): bool { if ( ! function_exists( 'wp_script_modules' ) ) { return true; } $modules = wp_script_modules(); if ( ! method_exists( $modules, 'get_queue' ) || ! method_exists( $modules, 'get_registered' ) ) { return true; } $store = '@woocommerce/stores/woocommerce/products'; $seen = []; $imports = static function ( string $id ) use ( $modules, $store, &$seen, &$imports ): bool { if ( '' === $id || isset( $seen[ $id ] ) ) { return false; } $seen[ $id ] = true; $module = $modules->get_registered( $id ); foreach ( (array) ( is_array( $module ) ? ( $module['dependencies'] ?? [] ) : [] ) as $dep ) { $dep = (string) ( is_array( $dep ) ? ( $dep['id'] ?? '' ) : $dep ); if ( $store === $dep ) { return true; } if ( ! in_array( $dep, self::PRODUCT_STATE_SAFE, true ) && $imports( $dep ) ) { return true; } } return false; }; foreach ( (array) $modules->get_queue() as $id ) { $id = (string) $id; if ( in_array( $id, self::PRODUCT_STATE_SAFE, true ) ) { continue; } foreach ( self::PRODUCT_STATE_READERS as $reader ) { if ( $id === $reader || str_starts_with( $id, $reader . '-' ) ) { return true; } } $seen = []; if ( $imports( $id ) ) { return true; } } return false; } public static function inline_styles_limit( $limit ): int { if ( ! \AIEO_Engine_Settings::get( 'inline_critical_css', true ) || UsedCss::active() ) { return (int) $limit; } return (int) apply_filters( 'rooster/performance/inline_styles_limit', max( (int) $limit, 160000 ) ); } public static function local_style_paths(): void { if ( ! \AIEO_Engine_Settings::get( 'inline_critical_css', true ) || UsedCss::active() ) { return; } $styles = wp_styles(); $roots = [ untrailingslashit( content_url() ) => WP_CONTENT_DIR, untrailingslashit( includes_url() ) => ABSPATH . WPINC, untrailingslashit( site_url() ) => untrailingslashit( ABSPATH ), ]; $pending = (array) $styles->queue; $seen = []; while ( $pending ) { $handle = (string) array_pop( $pending ); if ( isset( $seen[ $handle ] ) ) { continue; } $seen[ $handle ] = true; foreach ( (array) ( $styles->registered[ $handle ]->deps ?? [] ) as $dep ) { $src = $styles->registered[ $dep ]->src ?? null; if ( is_string( $src ) && '' !== $src && ! in_array( $dep, $styles->queue, true ) ) { $styles->enqueue( $dep ); } $pending[] = (string) $dep; } } foreach ( (array) $styles->queue as $handle ) { $style = $styles->registered[ $handle ] ?? null; if ( ! $style || ! is_string( $style->src ) || '' === $style->src || $styles->get_data( $handle, 'path' ) ) { continue; } if ( ! in_array( (string) ( $style->args ?: 'all' ), [ 'all', 'screen' ], true ) || $styles->get_data( $handle, 'conditional' ) ) { continue; } $src = (string) strtok( $style->src, '?' ); if ( str_starts_with( $src, '/' ) && ! str_starts_with( $src, '//' ) ) { $src = site_url( $src ); } $path = ''; foreach ( $roots as $url => $dir ) { if ( str_starts_with( $src, $url . '/' ) ) { $path = $dir . substr( $src, strlen( $url ) ); break; } } if ( '' === $path || ! str_ends_with( $path, '.css' ) || ! is_file( $path ) || filesize( $path ) > 65536 ) { continue; } if ( ! apply_filters( 'rooster/performance/inline_style', true, $handle ) ) { continue; } $styles->add_data( $handle, 'path', wp_normalize_path( $path ) ); } } public static function note_social_service( $content, $block ) { $service = sanitize_key( (string) ( $block['attrs']['service'] ?? '' ) ); if ( '' !== $service ) { self::$social_services[ $service ] = true; } return $content; } public static function trim_social_links(): void { $handle = 'wp-block-social-links'; $styles = wp_styles(); if ( ! isset( $styles->registered[ $handle ] ) || ! wp_style_is( $handle, 'enqueued' ) || Defer::is_full() || ! apply_filters( 'rooster/performance/trim_social_links', true ) ) { return; } $path = (string) $styles->get_data( $handle, 'path' ); if ( '' === $path || ! is_readable( $path ) ) { return; } $used = self::$social_services; $css = (string) preg_replace_callback( '/[^{}]*\.wp-social-link-[a-z0-9-]+[^{}]*\{[^{}]*\}/', static function ( array $rule ) use ( $used ): string { preg_match_all( '/\.wp-social-link-([a-z0-9-]+)/', $rule[0], $services ); foreach ( $services[1] as $service ) { if ( isset( $used[ $service ] ) ) { return $rule[0]; } } return ''; }, (string) file_get_contents( $path ) ); $styles->registered[ $handle ]->src = false; unset( $styles->registered[ $handle ]->extra['path'] ); $after = (array) $styles->get_data( $handle, 'after' ); array_unshift( $after, '/*rooster:trimmed*/' . $css ); $styles->add_data( $handle, 'after', $after ); } private static array $withheld = []; private static array $rendered = []; private static array $restored = []; private static array $restore_on = []; private static bool $late = false; public static function withhold_plugin_styles(): void { $policy = (array) apply_filters( 'rooster/assets/plugin_style_policy', [ 'rooster-lms' => [ 'slot' => 'rooster-lms/', 'post_types' => [ 'rooster_course', 'rooster_lesson', 'rooster_quiz', 'rooster_person' ], 'taxonomies' => [ 'rooster_course_category', 'rooster_lesson_category' ] ], 'woocommerce' => [ 'handles' => [ 'wc-blocks-style', 'woocommerce-blocktheme', 'rooster-woocommerce' ], 'blocks' => [ 'woocommerce', '!woocommerce/customer-account' ], 'when' => [ self::class, 'is_store_page' ] ], 'aieo' => [ 'handles' => [ 'aieo-card', 'aieo-swatches', 'aieo-waitlist', 'aieo-brand-gifts', 'aieo-complementary', 'aieo-product-categories' ], 'blocks' => [ 'aieo', 'woocommerce/product*', 'woocommerce/single-product', 'woocommerce/all-products', 'woocommerce/handpicked-products', 'woocommerce/featured-product', 'woocommerce/related-products' ], 'when' => [ self::class, 'is_store_page' ], ], ] ); $slot = (string) rooster_current_slot(); $kept = class_exists( PluginPages::class ) ? PluginPages::keeps() : []; foreach ( $policy as $prefix => $rule ) { $rule = (array) $rule; if ( self::$late && isset( self::$restored[ $prefix ] ) ) { continue; } if ( in_array( (string) $prefix, $kept, true ) ) { continue; } if ( '' !== $slot && str_starts_with( $slot, (string) ( $rule['slot'] ?? '~' ) ) ) { continue; } $types = (array) ( $rule['post_types'] ?? [] ); if ( $types && ( is_singular( $types ) || is_post_type_archive( $types ) ) ) { continue; } if ( ! empty( $rule['taxonomies'] ) && is_tax( (array) $rule['taxonomies'] ) ) { continue; } $spaces = array_map( 'strval', (array) ( $rule['blocks'] ?? [] ) ); if ( self::seen( $spaces, array_keys( self::$rendered ) ) ) { continue; } if ( ! empty( $rule['when'] ) && is_callable( $rule['when'] ) && call_user_func( $rule['when'] ) ) { continue; } $prefixes = (array) ( $rule['handles'] ?? [ $prefix ] ); foreach ( (array) wp_styles()->queue as $handle ) { foreach ( $prefixes as $start ) { if ( str_starts_with( (string) $handle, (string) $start ) ) { wp_dequeue_style( (string) $handle ); self::$withheld[ $prefix ][] = (string) $handle; self::$restore_on[ $prefix ] = array_unique( array_merge( [ (string) $prefix ], $spaces ) ); break; } } } } } public static function withhold_late_plugin_styles(): void { self::$late = true; self::withhold_plugin_styles(); self::$late = false; } public static function mark_rendered( string $namespace ): void { self::$rendered[ $namespace ] = true; } private static function seen( array $patterns, array $names ): bool { $match = static function ( string $pattern, string $name ): bool { return str_ends_with( $pattern, '*' ) ? str_starts_with( $name, substr( $pattern, 0, -1 ) ) : ( $name === $pattern || ( ! str_contains( $pattern, '/' ) && str_starts_with( $name, $pattern . '/' ) ) ); }; $out = []; foreach ( $patterns as $pattern ) { if ( str_starts_with( (string) $pattern, '!' ) ) { $out[] = substr( (string) $pattern, 1 ); } } foreach ( $names as $name ) { $name = (string) $name; foreach ( $out as $pattern ) { if ( $match( $pattern, $name ) ) { continue 2; } } foreach ( $patterns as $pattern ) { if ( ! str_starts_with( (string) $pattern, '!' ) && $match( (string) $pattern, $name ) ) { return true; } } } return false; } public static function is_store_page(): bool { return ( function_exists( 'is_woocommerce' ) && is_woocommerce() ) || ( function_exists( 'is_cart' ) && is_cart() ) || ( function_exists( 'is_checkout' ) && is_checkout() ) || ( function_exists( 'is_account_page' ) && is_account_page() ); } public static function restore_plugin_styles( $content, $block ) { $name = (string) ( $block['blockName'] ?? '' ); if ( '' !== $name ) { self::$rendered[ $name ] = true; foreach ( self::$withheld as $prefix => $handles ) { if ( $handles && self::seen( self::$restore_on[ $prefix ] ?? [ (string) $prefix ], [ $name ] ) ) { foreach ( $handles as $handle ) { wp_enqueue_style( $handle ); } self::$withheld[ $prefix ] = []; self::$restored[ $prefix ] = true; } } } return $content; } public static function no_emoji(): void { remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); remove_action( 'embed_head', 'print_emoji_detection_script' ); remove_action( 'wp_enqueue_scripts', 'wp_enqueue_emoji_styles' ); remove_action( 'enqueue_embed_scripts', 'wp_enqueue_emoji_styles' ); remove_action( 'wp_print_styles', 'print_emoji_styles' ); remove_filter( 'the_content_feed', 'wp_staticize_emoji' ); remove_filter( 'comment_text_rss', 'wp_staticize_emoji' ); remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); add_filter( 'emoji_svg_url', '__return_false' ); add_filter( 'wp_resource_hints', [ self::class, 'drop_emoji_hint' ], 10, 2 ); add_action( 'admin_init', static function (): void { remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); remove_action( 'admin_enqueue_scripts', 'wp_enqueue_emoji_styles' ); remove_action( 'admin_print_styles', 'print_emoji_styles' ); } ); } public static function drop_emoji_hint( $urls, $relation ): array { $urls = (array) $urls; if ( 'dns-prefetch' !== $relation ) { return $urls; } return array_values( array_filter( $urls, static fn ( $url ): bool => ! empty( $url ) && ! str_contains( is_array( $url ) ? (string) ( $url['href'] ?? '' ) : (string) $url, 's.w.org' ) ) ); } public static function speculation_configuration( $config ) { $policy = (string) \AIEO_Engine_Settings::get( 'speculation', 'moderate' ); if ( 'off' === $policy ) { return null; } return [ 'mode' => 'prerender', 'eagerness' => in_array( $policy, [ 'conservative', 'moderate', 'eager' ], true ) ? $policy : 'moderate' ]; } public static function speculation_exclusions( $paths, $mode = 'prerender' ): array { $paths = (array) $paths; $slugs = []; if ( function_exists( 'wc_get_page_id' ) ) { foreach ( [ 'cart', 'checkout', 'myaccount' ] as $page ) { $id = (int) wc_get_page_id( $page ); if ( $id > 0 ) { $slugs[] = get_post_field( 'post_name', $id ); } } $slugs[] = 'wc-api'; } foreach ( [ 'rooster_lesson', 'rooster_quiz' ] as $type ) { $object = get_post_type_object( $type ); if ( $object && ! empty( $object->rewrite['slug'] ) ) { $slugs[] = (string) $object->rewrite['slug']; } } foreach ( [ 'portal_page_id', 'account_page_id', 'registration_page_id' ] as $key ) { $id = (int) apply_filters( 'rooster/speculation/lms_page_id', 0, $key ); if ( $id > 0 ) { $slugs[] = get_post_field( 'post_name', $id ); } } $slugs = array_values( array_unique( array_filter( array_map( 'strval', $slugs ) ) ) ); $prefixes = [ '' ]; $languages = apply_filters( 'wpml_active_languages', null, [ 'skip_missing' => 0 ] ); if ( is_array( $languages ) ) { foreach ( array_keys( $languages ) as $code ) { $prefixes[] = '/' . $code; } } elseif ( function_exists( 'pll_languages_list' ) ) { foreach ( (array) pll_languages_list() as $code ) { $prefixes[] = '/' . $code; } } foreach ( $prefixes as $prefix ) { foreach ( $slugs as $slug ) { $paths[] = $prefix . '/' . $slug . '/*'; } $paths[] = $prefix . '/wp-json/*'; } $paths[] = '/*\\?add-to-cart=*'; $paths[] = '/*\\?*rooster_*'; $paths[] = '/*\\?*aieo_*'; return array_values( array_unique( (array) apply_filters( 'rooster/speculation/exclude', $paths, $mode ) ) ); } public static function policy(): string { return (string) \AIEO_Engine_Settings::get( 'third_party', 'delay' ); } public static function handles(): array { $done = did_action( 'wp_head' ) || is_admin() || wp_doing_ajax(); if ( null !== self::$handles && ! ( $done && self::$handles_done ) ) { self::$handles = null; } self::$handles_done = $done; if ( null === self::$handles ) { $defaults = [ 'google-recaptcha' => [ 'trigger' => 'recaptcha' ], 'wpcf7-recaptcha' => [ 'trigger' => 'recaptcha' ], 'gforms_recaptcha' => [ 'trigger' => 'recaptcha' ], 'google-tag-manager' => [ 'trigger' => 'human', 'consent' => 'analytics' ], 'google-analytics' => [ 'trigger' => 'human', 'consent' => 'analytics' ], 'woocommerce-google-analytics-integration' => [ 'trigger' => 'human', 'consent' => 'analytics' ], 'facebook-for-woocommerce-pixel' => [ 'trigger' => 'human', 'consent' => 'marketing' ], 'sourcebuster-js' => [ 'trigger' => 'human', 'consent' => 'marketing' ], 'wc-order-attribution' => [ 'trigger' => 'human', 'consent' => 'marketing' ], ]; $allow = (array) \AIEO_Engine_Settings::get( 'third_party_allow', [] ); self::$handles = 'delay' === self::policy() ? (array) apply_filters( 'rooster/third_party/handles', $defaults ) : []; foreach ( $allow as $handle ) { unset( self::$handles[ $handle ] ); } } return self::$handles; } public static function registered(): array { if ( ! self::$registered && 'delay' === self::policy() ) { self::$registered = (array) apply_filters( 'rooster/third_party/register', [] ); } return self::$registered; } public static function stub_attributes( $attributes ) { if ( 'module' === ( $attributes['type'] ?? '' ) ) { return self::module_stub( (array) $attributes ); } $id = (string) ( $attributes['id'] ?? '' ); if ( '' === $id || isset( $attributes['data-rooster-third-party'] ) ) { return $attributes; } $handle = preg_replace( '/-js(-(before|after|extra|translations))?$/', '', $id ); $map = self::handles(); if ( ! isset( $map[ $handle ] ) ) { return self::delay_attributes( (array) $attributes, $id, (string) $handle ); } self::$stubs = true; $attributes['type'] = 'text/plain'; $attributes['data-rooster-third-party'] = $handle; $attributes['data-rooster-trigger'] = $map[ $handle ]['trigger'] ?? 'human'; if ( ! empty( $map[ $handle ]['consent'] ) ) { $attributes['data-rooster-consent'] = $map[ $handle ]['consent']; } unset( $attributes['defer'], $attributes['async'] ); return $attributes; } public static function print_registered(): void { foreach ( self::registered() as $handle => $script ) { $attrs = [ 'type' => 'text/plain', 'data-rooster-third-party' => (string) $handle, 'data-rooster-trigger' => (string) ( $script['trigger'] ?? 'human' ), ]; if ( ! empty( $script['consent'] ) ) { $attrs['data-rooster-consent'] = (string) $script['consent']; } foreach ( (array) ( $script['attrs'] ?? [] ) as $k => $v ) { $attrs[ sanitize_key( (string) $k ) ] = (string) $v; } if ( ! empty( $script['src'] ) ) { $attrs['src'] = (string) $script['src']; } self::$stubs = true; echo ' $v ) { echo ' ' . esc_attr( $k ) . '="' . esc_attr( $v ) . '"'; } echo '>' . ( empty( $script['src'] ) ? (string) ( $script['inline'] ?? '' ) : '' ) . "\n"; } } public static function has_stubs(): bool { return self::$stubs || ( ! is_admin() && self::registered() ); } public static function finish_response(): void { if ( ! self::finishes_response() ) { return; } if ( function_exists( 'WC' ) && WC()->session instanceof \WC_Session_Handler && ! Defer::is_full() ) { WC()->session->save_data(); } if ( function_exists( 'fastcgi_finish_request' ) ) { fastcgi_finish_request(); } elseif ( function_exists( 'litespeed_finish_request' ) ) { litespeed_finish_request(); } } public static function save_session_before_answer( $passthrough = null ) { self::save_session_now(); return $passthrough; } public static function save_session_before_rest_answer( $response, $server = null, $request = null ) { if ( $request instanceof \WP_REST_Request && ! in_array( strtoupper( (string) $request->get_method() ), [ 'GET', 'HEAD', 'OPTIONS' ], true ) ) { self::save_session_now(); } return $response; } private static function save_session_now(): void { if ( Defer::is_full() || ! function_exists( 'WC' ) ) { return; } $wc = WC(); if ( is_object( $wc ) && isset( $wc->session ) && is_object( $wc->session ) && method_exists( $wc->session, 'save_data' ) ) { $wc->session->save_data(); } } public static function finishes_response( $status = null ): bool { $status = null === $status ? http_response_code() : $status; $method = strtoupper( (string) ( $_SERVER['REQUEST_METHOD'] ?? '' ) ); $ok = in_array( $method, [ 'GET', 'HEAD' ], true ) && 200 === $status && did_action( 'template_redirect' ) && ! wp_doing_ajax() && ! wp_doing_cron() && ! wp_is_serving_rest_request() && ! Footprint::active(); return (bool) apply_filters( 'rooster/performance/finish_response', $ok ); } public static function purge_page_cache(): void { static $done = false; if ( $done ) { return; } $done = true; if ( function_exists( 'rocket_clean_domain' ) ) { rocket_clean_domain(); } if ( has_action( 'litespeed_purge_all' ) ) { do_action( 'litespeed_purge_all' ); } if ( function_exists( 'wp_cache_clear_cache' ) ) { wp_cache_clear_cache(); } if ( function_exists( 'w3tc_flush_all' ) ) { w3tc_flush_all(); } if ( function_exists( 'sg_cachepress_purge_cache' ) ) { sg_cachepress_purge_cache(); } if ( has_action( 'rt_nginx_helper_purge_all' ) ) { do_action( 'rt_nginx_helper_purge_all' ); } if ( has_action( 'cloudflare_purge_everything' ) ) { do_action( 'cloudflare_purge_everything' ); } if ( function_exists( 'wp_cache_flush_group' ) && wp_cache_supports( 'flush_group' ) ) { wp_cache_flush_group( 'rooster' ); } do_action( 'rooster/cache/purge' ); } public static function consent( string $category ): ?bool { $raw = $_COOKIE[ self::CONSENT_COOKIE ] ?? null; if ( null === $raw ) { return null; } $data = json_decode( stripslashes( (string) $raw ), true ); if ( ! is_array( $data ) ) { return null; } if ( 'necessary' === $category ) { return true; } return isset( $data[ $category ] ) ? (bool) $data[ $category ] : null; } }