get_id(); } else { $product_id = get_the_ID(); } } else { $product_id = isset( $attributes['productId'] ) ? absint( $attributes['productId'] ) : 0; } if ( ! $product_id ) { return ''; } // Get the product. $product = wc_get_product( $product_id ); if ( ! $product || 'publish' !== get_post_status( $product_id ) ) { return ''; } // Respect password-protected products. if ( post_password_required( $product_id ) ) { echo get_the_password_form( $product_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Core markup. return; } // Extract display settings. $layout = isset( $attributes['layout'] ) && 'media-right' === $attributes['layout'] ? 'media-right' : 'media-left'; $image_size = isset( $attributes['imageSize'] ) ? sanitize_key( $attributes['imageSize'] ) : 'large'; $show_price = ! isset( $attributes['showPrice'] ) || $attributes['showPrice']; $show_rating = ! isset( $attributes['showRating'] ) || $attributes['showRating']; $show_stock_status = ! isset( $attributes['showStockStatus'] ) || $attributes['showStockStatus']; $show_sale_badge = ! isset( $attributes['showSaleBadge'] ) || $attributes['showSaleBadge']; $show_short_desc = ! empty( $attributes['showShortDescription'] ); $show_add_to_cart = ! isset( $attributes['showAddToCart'] ) || $attributes['showAddToCart']; $show_variations = ! isset( $attributes['showVariations'] ) || $attributes['showVariations']; $min_height = isset( $attributes['minHeight'] ) ? $attributes['minHeight'] : '500px'; $focal_point = isset( $attributes['mediaFocalPoint'] ) ? $attributes['mediaFocalPoint'] : array( 'x' => 0.5, 'y' => 0.5, ); $allowed_alignments = array( 'top', 'center', 'bottom' ); $vertical_alignment = isset( $attributes['contentVerticalAlignment'] ) && in_array( $attributes['contentVerticalAlignment'], $allowed_alignments, true ) ? $attributes['contentVerticalAlignment'] : 'center'; // Validate image size. $allowed_sizes = array( 'medium', 'large', 'full' ); if ( ! in_array( $image_size, $allowed_sizes, true ) ) { $image_size = 'large'; } // Validate min height. $safe_min_height = ( $min_height && preg_match( '/^\d+(\.\d+)?(px|vh|vw|em|rem|%)$/', $min_height ) ) ? $min_height : '500px'; // Build focal point CSS. $focal_x = isset( $focal_point['x'] ) ? max( 0, min( 1, floatval( $focal_point['x'] ) ) ) * 100 : 50; $focal_y = isset( $focal_point['y'] ) ? max( 0, min( 1, floatval( $focal_point['y'] ) ) ) * 100 : 50; $object_position = $focal_x . '% ' . $focal_y . '%'; // Map vertical alignment. $align_map = array( 'top' => 'flex-start', 'center' => 'center', 'bottom' => 'flex-end', ); $content_justify = isset( $align_map[ $vertical_alignment ] ) ? $align_map[ $vertical_alignment ] : 'center'; // Only the first hero on a page is likely the LCP image; a second // fetchpriority=high just competes with it. static $hero_count = 0; ++$hero_count; $image_attrs = array( 'class' => 'airo-wp-product-showcase-hero__image', 'style' => 'object-position: ' . esc_attr( $object_position ) . ';', 'loading' => 'eager', ); if ( 1 === $hero_count ) { $image_attrs['fetchpriority'] = 'high'; } // Get product image. $image_id = $product->get_image_id(); $image_html = ''; if ( $image_id ) { $image_html = wp_get_attachment_image( $image_id, $image_size, false, $image_attrs ); } // Build wrapper attributes. $wrapper_class = 'airo-wp-product-showcase-hero airo-wp-product-showcase-hero--' . $layout; $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $wrapper_class, 'style' => '--airo-wp-psh-min-height: ' . esc_attr( $safe_min_height ) . '; --airo-wp-psh-content-justify: ' . esc_attr( $content_justify ) . ';', ) ); // Set up global product for WooCommerce template functions. // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited global $post; $original_post = $post; $original_product = isset( $GLOBALS['product'] ) ? $GLOBALS['product'] : null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- WooCommerce template functions require the global post object; restored below. $post = get_post( $product_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WooCommerce template functions require this global to be named exactly $product; swapped for loop context and restored below. $GLOBALS['product'] = $product; setup_postdata( $post ); ?>
>
is_on_sale() ) : ?>

get_name() ); ?>

get_price_html() ); ?>
get_average_rating() > 0 ) : ?>
get_average_rating(), $product->get_review_count() ) ); ?>
get_stock_status(); $availability = $product->get_availability(); $stock_text = ! empty( $availability['availability'] ) ? $availability['availability'] : ( $product->is_in_stock() ? __( 'In stock', 'airo-wp' ) : __( 'Out of stock', 'airo-wp' ) ); ?>
get_short_description() ) : ?>
get_short_description() ); ?>
is_type( 'variable' ) ) { woocommerce_template_single_add_to_cart(); } else { woocommerce_template_loop_add_to_cart(); } $add_to_cart_html = ob_get_clean(); echo str_replace( 'class="button', 'class="button wp-block-button__link wp-element-button', $add_to_cart_html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WooCommerce template output. ?>
post differs). // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $post = $original_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- restoring WooCommerce's original global $product after the loop; the name is fixed by WooCommerce. $GLOBALS['product'] = $original_product; if ( $post ) { setup_postdata( $post ); } } } airowp_render_product_showcase_hero( $attributes, $content, $block );