true. */ private static function get_non_deferrable_handles( $exclude ) { static $cache = null; if ( null !== $cache ) { return $cache; } $wp_scripts = wp_scripts(); $non_deferrable = array(); // First pass: find all explicitly excluded handles. foreach ( $wp_scripts->registered as $handle => $dep_obj ) { if ( self::matches_exclusion_pattern( $handle, $exclude ) ) { $non_deferrable[ $handle ] = true; } } // Second pass: for each excluded handle, mark all its dependencies // as non-deferrable too (breadth-first). $to_process = array_keys( $non_deferrable ); while ( ! empty( $to_process ) ) { $current = array_pop( $to_process ); if ( ! isset( $wp_scripts->registered[ $current ] ) ) { continue; } foreach ( $wp_scripts->registered[ $current ]->deps as $dep ) { if ( ! isset( $non_deferrable[ $dep ] ) ) { $non_deferrable[ $dep ] = true; $to_process[] = $dep; } } } $cache = $non_deferrable; return $cache; } /** * Speculative loading configuration. * * @param array $config Existing config. * * @return array */ public static function speculation_rules( $config ) { if ( ! I365_PO_Plugin::is_frontend_context() ) { return $config; } // Check per-page override. if ( I365_PO_Meta_Box::is_override_active( 'disable_speculation' ) ) { return $config; } $settings = I365_PO_Plugin::get_settings(); if ( I365_PO_Plugin::is_speculation_excluded( $settings['speculation_exclusions'] ) ) { return $config; } if ( 'auto' !== $settings['speculation_mode'] ) { $config['mode'] = $settings['speculation_mode']; } $config['eagerness'] = $settings['speculation_eagerness']; if ( ! empty( $settings['speculation_exclusions'] ) ) { $config['excludes'] = array(); foreach ( $settings['speculation_exclusions'] as $path ) { $config['excludes'][] = array( 'path' => $path, ); } } return $config; } /** * Output preconnect and preload hints. * * @return void */ public static function output_preloads() { if ( ! I365_PO_Plugin::is_frontend_context() ) { return; } $settings = I365_PO_Plugin::get_settings(); foreach ( $settings['preconnect_hosts'] as $host ) { $is_cross = ( wp_parse_url( $host, PHP_URL_HOST ) !== wp_parse_url( home_url(), PHP_URL_HOST ) ); $cross = $is_cross ? ' crossorigin' : ''; printf( '%3$s', esc_url( $host ), $cross, "\n" ); } if ( ! empty( $settings['preload_font'] ) ) { printf( '%s', esc_url( $settings['preload_font'] ), "\n" ); } if ( ! empty( $settings['preload_hero'] ) ) { if ( ! $settings['hero_preload_on_front'] || is_front_page() ) { printf( '%s', esc_url( $settings['preload_hero'] ), "\n" ); } } } /** * Clean REST and oEmbed link tags on the front end. * * @return void */ public static function clean_head_links() { if ( is_admin() ) { return; } $settings = I365_PO_Plugin::get_settings(); if ( $settings['remove_rest_link'] ) { remove_action( 'wp_head', 'rest_output_link_wp_head' ); } if ( $settings['remove_oembed_link'] ) { remove_action( 'wp_head', 'wp_oembed_add_discovery_links' ); } } /** * Mark LCP images as high priority when applicable. * * @param array $attr Attributes. * @param WP_Post $attachment Attachment. * @param string|array $size Size. * * @return array */ public static function lcp_priority( $attr, $attachment, $size ) { if ( ! I365_PO_Plugin::is_frontend_context() ) { return $attr; } if ( empty( $attr['loading'] ) && empty( $attr['fetchpriority'] ) ) { $attr['fetchpriority'] = 'high'; } return $attr; } /** * Disable lazy-loading on front page images for sanity. * * @param bool $default Default value. * @param string $tag Tag name. * * @return bool */ public static function lazy_loading_sanity( $default, $tag ) { if ( ! I365_PO_Plugin::is_frontend_context() ) { return $default; } // Check per-page override to force lazy-loading. if ( I365_PO_Meta_Box::is_override_active( 'force_lazy_load' ) ) { return $default; // Keep default (lazy-loading enabled). } if ( 'img' === $tag && is_front_page() ) { return false; } return $default; } /** * Enqueue stylesheet with preload metadata to satisfy plugin checks. * * @return void */ public static function enqueue_stylesheet_preload() { if ( ! I365_PO_Plugin::is_frontend_context() ) { return; } $settings = I365_PO_Plugin::get_settings(); if ( empty( $settings['preload_stylesheet'] ) ) { return; } $url = esc_url_raw( $settings['preload_stylesheet'] ); $handle = 'i365-po-preload-style'; wp_enqueue_style( $handle, $url, array(), I365_PO_VERSION ); // Inform WordPress this should be preloaded. wp_style_add_data( $handle, 'preload', true ); } /* |-------------------------------------------------------------------------- | JavaScript Delay Until Interaction |-------------------------------------------------------------------------- */ /** * Delay non-critical scripts until user interaction. * * @param string $tag Script tag HTML. * @param string $handle Script handle. * @param string $src Script URL. * * @return string */ public static function delay_scripts( $tag, $handle, $src ) { if ( ! I365_PO_Plugin::is_frontend_context() ) { return $tag; } // Check per-page override. if ( I365_PO_Meta_Box::is_override_active( 'disable_delay_js' ) ) { return $tag; } $settings = I365_PO_Plugin::get_settings(); $exclude = $settings['delay_js_exclude']; // Merge per-page excluded scripts. $page_excluded = I365_PO_Meta_Box::get_excluded_scripts(); if ( ! empty( $page_excluded ) ) { $exclude = array_merge( $exclude, $page_excluded ); } // Check if this script should be excluded from delay. if ( self::is_delay_excluded( $handle, $exclude ) ) { return $tag; } // Don't delay scripts that are already deferred/async or modules. if ( false !== stripos( $tag, ' async' ) || false !== stripos( $tag, ' type="module"' ) ) { return $tag; } // Don't delay inline scripts (no src). if ( empty( $src ) ) { return $tag; } // Transform the script tag to delay loading. // Change src to data-i365-src and add data-i365-delay marker. $tag = preg_replace( '/(]*)\ssrc=["\']([^"\']+)["\']([^>]*>)/i', '$1 data-i365-src="$2" data-i365-delay$3', $tag ); // Add type="text/plain" to prevent execution. $tag = preg_replace( '/( true. */ private static function get_non_delayable_handles( $exclude ) { static $cache = null; if ( null !== $cache ) { return $cache; } $wp_scripts = wp_scripts(); $non_delayable = array(); // First pass: find all explicitly excluded handles. foreach ( $wp_scripts->registered as $handle => $dep_obj ) { if ( self::matches_delay_exclusion_pattern( $handle, $exclude ) ) { $non_delayable[ $handle ] = true; } } // Second pass: mark all dependencies of excluded handles. $to_process = array_keys( $non_delayable ); while ( ! empty( $to_process ) ) { $current = array_pop( $to_process ); if ( ! isset( $wp_scripts->registered[ $current ] ) ) { continue; } foreach ( $wp_scripts->registered[ $current ]->deps as $dep ) { if ( ! isset( $non_delayable[ $dep ] ) ) { $non_delayable[ $dep ] = true; $to_process[] = $dep; } } } $cache = $non_delayable; return $cache; } /** * Output the delay loader script inline in head. * * @return void */ public static function output_delay_loader() { if ( ! I365_PO_Plugin::is_frontend_context() ) { return; } $settings = I365_PO_Plugin::get_settings(); $timeout = absint( $settings['delay_js_timeout'] ); // Read and output the loader script. $loader_file = I365_PO_PLUGIN_DIR . 'assets/js/delay-interaction.js'; if ( ! file_exists( $loader_file ) ) { return; } $handle = fopen( $loader_file, 'r' ); $script = $handle ? fread( $handle, filesize( $loader_file ) ) : ''; if ( $handle ) { fclose( $handle ); } if ( empty( $script ) ) { return; } // Output timeout variable and inline script. // The script content is from a trusted plugin file, not user input. echo '\n"; } /* |-------------------------------------------------------------------------- | Heartbeat API Control |-------------------------------------------------------------------------- */ /** * Control WordPress Heartbeat API behavior. * * @return void */ public static function control_heartbeat() { // This method is a placeholder for initialization. // Actual work is done by deregister_heartbeat and modify_heartbeat_settings. } /** * Deregister the heartbeat script. * * @return void */ public static function deregister_heartbeat() { wp_deregister_script( 'heartbeat' ); } /** * Modify heartbeat settings to reduce frequency. * * @param array $settings Heartbeat settings. * * @return array */ public static function modify_heartbeat_settings( $settings ) { $opts = I365_PO_Plugin::get_settings(); $settings['interval'] = absint( $opts['heartbeat_interval'] ); return $settings; } /* |-------------------------------------------------------------------------- | Query String Removal |-------------------------------------------------------------------------- */ /** * Remove version query strings from static resources. * * @param string $src Resource URL. * @param string $handle Resource handle. * * @return string */ public static function remove_query_strings( $src, $handle ) { // Never strip version strings from admin assets. if ( is_admin() ) { return $src; } // Only process if there's a version query string. if ( strpos( $src, '?ver=' ) !== false || strpos( $src, '&ver=' ) !== false ) { $src = remove_query_arg( 'ver', $src ); } return $src; } }