ipt>'; foreach ( $tags_match[0] as $i => $tag ) { // Strip query args. $path = wp_parse_url( $tags_match[2][ $i ], PHP_URL_PATH ); // Check if this file should be deferred. if ( isset( $excluded_css[ $path ] ) ) { continue; } if ( preg_match( '/media\s*=\s*[\'"]print[\'"]/i', $tags_match[0][ $i ] ) ) { continue; } $preload = str_replace( 'stylesheet', 'preload', $tags_match[1][ $i ] ); $onload = preg_replace( '~' . preg_quote( $tags_match[3][ $i ], '~' ) . '~iU', ' data-rocket-async="style" as="style" onload="" onerror="this.removeAttribute(\'data-rocket-async\')" ' . $tags_match[3][ $i ] . '>', $tags_match[3][ $i ] ); $tag = str_replace( $tags_match[3][ $i ] . '>', $onload, $tag ); $tag = str_replace( $tags_match[1][ $i ], $preload, $tag ); $tag = str_replace( 'onload=""', 'onload="this.onload=null;this.rel=\'stylesheet\'"', $tag ); $tag = preg_replace( '/(id\s*=\s*[\"\'](?:[^\"\']*)*[\"\'])/i', '', $tag ); $buffer = str_replace( $tags_match[0][ $i ], $tag, $buffer ); $noscripts .= $tags_match[0][ $i ]; } $noscripts .= ''; return str_replace( '', $noscripts . '', $buffer ); } /** * Regenerates the CPCSS when switching theme if the option is active. * * @since 3.3 */ public function maybe_regenerate_cpcss() { if ( ! $this->options->get( 'async_css' ) ) { return; } if ( ! $this->is_mobile_cpcss_active() ) { $this->critical_css->process_handler( 'default', 'all' ); return; } $this->critical_css->process_handler( 'all' ); } /** * Checks if mobile CPCSS is active. * * @since 3.6 * * @return boolean CPCSS active or not. */ private function is_mobile_cpcss_active() { return ( $this->options->get( 'async_css', 0 ) && $this->options->get( 'cache_mobile', 0 ) && $this->options->get( 'do_caching_mobile_files', 0 ) ) && $this->options->get( 'async_css_mobile', 0 ); } /** * Checks if we should async CSS * * @since 3.6.2.1 * * @return boolean */ private function should_async_css() { if ( rocket_get_constant( 'DONOTROCKETOPTIMIZE' ) ) { return false; } if ( ! $this->options->get( 'async_css', 0 ) ) { return false; } return ! is_rocket_post_excluded_option( 'async_css' ); } /** * Stops the critical CSS generation. * * @since 3.10 * * @return void */ public function stop_critical_css_generation() { $this->critical_css->stop_generation(); delete_transient( 'rocket_critical_css_generation_process_running' ); delete_transient( 'rocket_critical_css_generation_process_complete' ); } /** * Stops the critical CSS generation when deactivating the async CSS option via CLI and remove the notices. * * @note CL. * @since 3.12.6 */ public function stop_process_on_cli_deactivation() { $this->stop_critical_css_generation(); } /** * Sanitizes options * * @param array $input Array of values submitted from the form. * @param \WP_Rocket\Engine\Admin\Settings\Settings $settings Settings class instance. * * @note CL * @return array */ public function sanitize_options( $input, $settings ) : array { $is_wp2_env = ( function_exists( 'clsop_is_wp2_environment' ) && clsop_is_wp2_environment() ); // WP2 branch: checkbox only toggles OCD and triggers clwpos-user; no async_css* normalization. if ( $is_wp2_env ) { // Only record intended action; do not change unrelated options here. $this->cpcss_toggle_action = null; $option_key = rocket_get_constant( 'WP_ROCKET_SLUG', 'wp_rocket_settings' ); $posted = filter_input( INPUT_POST, $option_key, FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! is_array( $posted ) && isset( $_POST[ $option_key ] ) && is_array( $_POST[ $option_key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $posted = array_map( 'sanitize_text_field', wp_unslash( $_POST[ $option_key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } $posted = is_array( $posted ) ? $posted : []; if ( array_key_exists( 'optimize_css_delivery', $posted ) ) { $raw_value = $posted['optimize_css_delivery']; $ocd_checked = (int) sanitize_text_field( (string) $raw_value ) === 1; $this->cpcss_toggle_action = $ocd_checked ? 'enable' : 'disable'; set_transient( 'clsop_ui_cpcss_toggle_action', $this->cpcss_toggle_action, MINUTE_IN_SECONDS ); return array_merge( (array) $input, [ 'optimize_css_delivery' => $ocd_checked ? '1' : 0 ] ); } // Unchecked: payload exists but field is absent. if ( is_array( $posted ) && ! empty( $posted ) && ! array_key_exists( 'optimize_css_delivery', $posted ) ) { $this->cpcss_toggle_action = 'disable'; set_transient( 'clsop_ui_cpcss_toggle_action', $this->cpcss_toggle_action, MINUTE_IN_SECONDS ); return array_merge( (array) $input, [ 'optimize_css_delivery' => 0 ] ); } // Fallback to sanitized input if POST is unavailable in this context. if ( array_key_exists( 'optimize_css_delivery', $input ) ) { $ocd_checked = (int) sanitize_text_field( (string) $input['optimize_css_delivery'] ) === 1; $this->cpcss_toggle_action = $ocd_checked ? 'enable' : 'disable'; set_transient( 'clsop_ui_cpcss_toggle_action', $this->cpcss_toggle_action, MINUTE_IN_SECONDS ); return array_merge( (array) $input, [ 'optimize_css_delivery' => $ocd_checked ? '1' : 0 ] ); } return $input; } // Non-WP2 branch. $feature_enabled = (int) get_rocket_option( 'async_css', 0 ) === 1; $uuid = (string) get_rocket_option( 'ccss_awp_unique_id', '' ); // CL: In standalone mode, allow user to toggle the checkbox via UI. // Preserve existing uuid if any. if ( function_exists( 'clsop_is_standalone' ) && clsop_is_standalone() ) { if ( ! empty( $input['async_css'] ) || ! empty( $input['optimize_css_delivery'] ) ) { $input['ccss_awp_unique_id'] = $uuid; // Preserve existing, not used for API auth. $input['async_css'] = 1; $input['async_css_mobile'] = 1; } return $input; } // CLOS mode: preserve legacy normalization (managed by CLI). if ( ! $feature_enabled ) { // No normalization when feature disabled; keep input as-is. return $input; } // If input is empty: UUID first (if any), then enforced keys in fixed order. if ( empty( $input ) || ! is_array( $input ) ) { $normalized = []; if ( '' !== $uuid ) { $normalized['ccss_awp_unique_id'] = $uuid; } $normalized += [ 'async_css' => 1, 'async_css_mobile' => 1, 'remove_unused_css' => 0, 'optimize_css_delivery' => '1', ]; return array_merge( (array) $input, $normalized ); } // Non-empty input: enforce values and put UUID last to match fixtures. $normalized = [ 'async_css' => 1, 'async_css_mobile' => 1, 'remove_unused_css' => 0, 'optimize_css_delivery' => '1', ]; if ( '' !== $uuid ) { $normalized['ccss_awp_unique_id'] = $uuid; } return array_merge( (array) $input, $normalized ); } /** * After options are saved, if OCD was checked in WP2, enable hosting feature via clwpos-user. * * @param array $value New options. * @return void */ public function on_options_changed_after_ui( $value ) { // Retrieve recorded action from transient if property is not set. if ( null === $this->cpcss_toggle_action ) { $this->cpcss_toggle_action = get_transient( 'clsop_ui_cpcss_toggle_action' ); delete_transient( 'clsop_ui_cpcss_toggle_action' ); } if ( null === $this->cpcss_toggle_action ) { return; } $env_is_wp2 = ( function_exists( 'clsop_is_wp2_environment' ) && clsop_is_wp2_environment() ); // Allow direct override of the client (e.g., tests) before consulting the container. $client = apply_filters( 'rocket_awp_clwpos_user_client', null ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound if ( ! is_object( $client ) ) { $container = apply_filters( 'rocket_container', null ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound if ( ! is_object( $container ) || ! method_exists( $container, 'get' ) ) { return; } $client = $container->get( 'awp_clwpos_user' ); } if ( ! is_object( $client ) ) { return; } // Only proceed if WP2 env OR the client/binary is available. if ( ! $env_is_wp2 ) { if ( ! method_exists( $client, 'is_available' ) || ! $client->is_available() ) { return; } } if ( 'disable' === $this->cpcss_toggle_action && method_exists( $client, 'disable_critical_css_current_site' ) ) { $client->disable_critical_css_current_site(); } elseif ( 'enable' === $this->cpcss_toggle_action && method_exists( $client, 'enable_critical_css_current_site' ) ) { $client->enable_critical_css_current_site(); } // Reset the recorded action. $this->cpcss_toggle_action = null; } /** * Display a notice to pass from CPCSS to RUCSS. * * @return void */ public function switch_to_rucss_notice() { // CL: removed. } /** * Switch to RUCSS. * * @return void */ public function switch_to_rucss() { // CL: removed. } /** * CL: Enable inline CSS for Google Fonts on mobile when aggressive optimization is active. * * This eliminates render-blocking font CSS requests on mobile devices by inlining * the font-face declarations directly into the HTML. * * @since 3.x * * @param bool $inline Whether to inline Google Fonts CSS. * @return bool True to inline fonts CSS on mobile with aggressive optimization. */ public function maybe_inline_fonts_on_mobile( $inline ) { // Already enabled by another filter. if ( $inline ) { return $inline; } // Check if Critical CSS (async_css) is enabled. if ( ! $this->options->get( 'async_css', 0 ) ) { return $inline; } // Check if aggressive mobile optimization is enabled. if ( ! $this->options->get( 'async_css_mobile_aggressive', 0 ) ) { return $inline; } // Only apply on mobile devices. if ( ! wp_is_mobile() ) { return $inline; } return true; } /** * CL: Filter CSS exclusions for aggressive mobile optimization. * * Reduces the list of CSS files excluded from async loading on mobile devices, * keeping only critical patterns like admin-bar and dashicons. * * @since 3.x * * @param array $exclusions Current list of CSS files excluded from async loading. * @return array Reduced list of exclusions for mobile optimization. */ public function aggressive_mobile_css_optimization( $exclusions ) { if ( ! is_array( $exclusions ) || empty( $exclusions ) ) { return $exclusions; } /** * Filter the list of CSS patterns to keep excluded even in aggressive mobile mode. * * Some CSS files should remain excluded from async loading even on mobile * to prevent layout shifts or critical functionality issues. * * @since 3.x * * @param array $keep_excluded Patterns of CSS files to keep excluded. */ $keep_excluded = (array) apply_filters( 'rocket_aggressive_mobile_css_keep_excluded', [ 'admin-bar', 'dashicons', 'wp-admin', ] ); if ( empty( $keep_excluded ) ) { return []; } $filtered_exclusions = []; foreach ( $exclusions as $exclusion ) { foreach ( $keep_excluded as $pattern ) { if ( false !== strpos( $exclusion, $pattern ) ) { $filtered_exclusions[] = $exclusion; break; } } } return $filtered_exclusions; } }