_above_the_fold_height = 1500; } elseif ( 'Medium' === $critical_threshold_height ) { $this->_above_the_fold_height = 1000; } else { $this->_above_the_fold_height = 500; } add_filter( 'et_builder_critical_css_enabled', '__return_true' ); // Dynamic CSS content shortcode modules. add_filter( 'et_dynamic_assets_modules_atf', [ $this, 'dynamic_assets_modules_atf' ], 10, 2 ); // Detect when renderining Above The Fold sections. add_filter( 'pre_do_shortcode_tag', [ $this, 'check_section_start' ], 99, 4 ); add_filter( 'do_shortcode_tag', [ $this, 'check_section_end' ], 99, 2 ); // Analyze Builder style manager. add_filter( 'et_builder_module_style_manager', [ $this, 'enable_builder' ] ); // Dynamic CSS content shortcode. add_filter( 'et_global_assets_list', [ $this, 'maybe_defer_global_asset' ], 99 ); if ( self::INCLUDE_REQUIRED ) { add_filter( 'et_dynamic_assets_atf_includes_required', '__return_true' ); } } /** * Defer some global assets if threshold is met. * * @param array $assets assets to defer. * * @since 4.10.0 * * @return array $assets assets to be deferred. */ public function maybe_defer_global_asset( $assets ) { $defer = [ 'et_divi_footer', 'et_divi_gutters_footer', 'et_divi_comments', ]; foreach ( $defer as $key ) { if ( isset( $assets[ $key ] ) ) { $assets[ $key ]['maybe_defer'] = true; } } return $assets; } /** * Force a PageResource to write its content on file, even when empty * * @param bool $force Default value. * @param array $resource Critical/Deferred PageResources. * * @since 4.10.0 * * @return bool */ public function force_resource_write( $force, $resource ) { $styles = $this->_builder_styles; if ( empty( $styles ) ) { return $force; } $forced_slugs = [ $styles['deferred']->slug, $styles['manager']->slug, ]; return in_array( $resource->slug, $forced_slugs, true ) ? true : $force; } /** * Analyze Builder style manager. * * @since 4.10.0 * * @param array $styles Style Managers. * * @return array */ public function enable_builder( $styles ) { $this->_builder_styles = $styles; // There are cases where external assets generation might be disabled at runtime, // ensure Critical CSS and Dynamic Assets use the same logic to avoid side effects. if ( ! et_should_generate_dynamic_assets() ) { $this->disable(); return $styles; } add_filter( 'et_core_page_resource_force_write', [ $this, 'force_resource_write' ], 10, 2 ); add_filter( 'et_core_page_resource_tag', [ $this, 'builder_style_tag' ], 10, 5 ); if ( et_builder_is_mod_pagespeed_enabled() ) { // PageSpeed filters out `preload` links so we gotta use `prefetch` but // Safari doesn't support the latter.... add_action( 'wp_body_open', [ $this, 'add_safari_prefetch_workaround' ], 1 ); } return $styles; } /** * Prints deferred Critical CSS stlyesheet. * * @param string $tag stylesheet template. * @param string $slug stylesheet slug. * @param string $scheme stylesheet URL. * @param string $onload stylesheet onload attribute. * * @since 4.10.0 * * @return string */ public function builder_style_tag( $tag, $slug, $scheme, $onload ) { $deferred = $this->_builder_styles['deferred']; $inlined = $this->_builder_styles['manager']; // reason: Stylsheet needs to be printed on demand. // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet // reason: Snake case requires refactor of PageResource.php. // phpcs:disable ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase switch ( $slug ) { case $deferred->slug: // Don't enqueue empty resources. if ( 0 === et_()->WPFS()->size( $deferred->path ) ) { return ''; } // Use 'prefetch' when Mod PageSpeed is detected because it removes 'preload' links. $rel = et_builder_is_mod_pagespeed_enabled() ? 'prefetch' : 'preload'; /** * Filter deferred styles rel attribute. * * Mod PageSpeed removes 'preload' links and we attempt to fix that by trying to detect if * the 'x-mod-pagespeed' (Apache) or 'x-page-speed' (Nginx) header is present and if it is, * replace 'preload' with 'prefetch'. However, in some cases, like when the request goes through * a CDN first, we are unable to detect the header. This hook can be used to change the 'rel' * attribute to use 'prefetch' when our et_builder_is_mod_pagespeed_enabled() function fails * to detect Mod PageSpeed. * * With that out of the way, the only reason I wrote this detailed description is to make Fabio proud. * * @since 4.11.3 * * @param string $rel */ $rel = apply_filters( 'et_deferred_styles_rel', $rel ); // Defer the stylesheet. $template = ''; return sprintf( $template, $slug, $scheme, $onload, $rel ); case $inlined->slug: // Inline the stylesheet. $template = "\n"; $content = et_()->WPFS()->get_contents( $inlined->path ); return sprintf( $template, $content ); } // phpcs:enable return $tag; } /** * Safari doesn't support `prefetch`...... * * @since 4.10.7 * * @return void */ public function add_safari_prefetch_workaround() { // .... so we turn it into `preload` using JS. ?> _atf_sections[ $attrs ] ) ) { $this->_atf_sections[ $attrs ]--; if ( ! $active ) { add_filter( $action, [ $this, 'set_style' ], 10 ); } } return $value; } /** * Remove `set_style` filter after rendering an ATF section. * * @since 4.10.0 * * @param string $output Shortcode output. * @param string $tag Shortcode name. * * @return string */ public function check_section_end( $output, $tag ) { static $section = 0; if ( 'et_pb_section' !== $tag ) { return $output; } $action = 'et_builder_set_style'; $filter = [ $this, 'set_style' ]; if ( has_filter( $action, $filter ) ) { remove_filter( $action, $filter, 10 ); } return $output; } /** * Filter used to analize content coming from Dynamic Access Class. * * @param array $value Default shortcodes list (empty). * @param string $content TB/Post Content. * * @since 4.10.0 * * @return array List of ATF shortcodes. */ public function dynamic_assets_modules_atf( $value, $content = '' ) { if ( empty( $content ) ) { return $value; } $modules = $this->extract( $content ); // Dynamic CSS content shortcode. add_filter( 'et_dynamic_assets_content', [ $this, 'dynamic_assets_content' ] ); return $modules; } /** * Returns splitted (ATF/BFT) Content. * * @since 4.10.0 * * @return stdClass */ public function dynamic_assets_content() { return $this->_content; } /** * While the filter is applied, any rendered style will be considered critical. * * @param array $style Style. * * @since 4.10.0 * * @return array */ public function set_style( $style ) { $style['critical'] = true; return $style; } /** * Parse Content into shortcodes. * * @param string $content TB/Post Content. * * @since 4.10.0 * * @return array|boolean */ public static function parse_shortcode( $content ) { static $regex; if ( false === strpos( $content, '[' ) ) { return false; } if ( empty( $regex ) ) { $regex = '/' . get_shortcode_regex() . '/'; // Add missing child shortcodes (because dynamically added). $existing = 'et_pb_pricing_tables'; $shortcodes = [ $existing, 'et_pb_pricing_item', ]; $regex = str_replace( $existing, join( '|', $shortcodes ), $regex ); } preg_match_all( $regex, $content, $matches, PREG_SET_ORDER ); return $matches; } /** * Estimates height to split Content in ATF/BTF. * * @param string $content TB/Post Content. * * @since 4.10.0 * * @return array List of ATF shortcodes. */ public function extract( $content ) { // Create root object when needed. if ( empty( $this->_root ) ) { $this->_root = (object) [ 'tag' => 'root', 'height' => 0, ]; } if ( $this->_root->height >= $this->_above_the_fold_height ) { // Do nothing when root already exists and its height >= treshold. return []; } $shortcodes = self::parse_shortcode( $content ); if ( ! is_array( $shortcodes ) ) { return []; } $shortcodes = array_reverse( $shortcodes ); $is_above_the_fold = true; $root = $this->_root; $root->count = count( $shortcodes ); $stack = [ $root ]; $parent = end( $stack ); $tags = []; $atf_content = ''; $btf_content = ''; $structure_slugs = [ 'et_pb_section', 'et_pb_row', 'et_pb_row_inner', 'et_pb_column', 'et_pb_column_inner', ]; $section = ''; $section_shortcode = ''; // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition while ( $is_above_the_fold && $shortcode = array_pop( $shortcodes ) ) { list( $raw,, $tag, $attrs,, $content ) = $shortcode; $tags[] = $tag; $children = self::parse_shortcode( $content ); $element = (object) [ 'tag' => $tag, 'children' => [], 'height' => 0, 'margin' => 0, 'padding' => 0, 'attrs' => [], ]; switch ( $tag ) { case 'et_pb_pricing_table': $lines = array_filter( explode( "\n", str_replace( array( '
', '
', '