| 1 |
<?php |
| 2 |
/** |
| 3 |
* Single Product Gallery render template. |
| 4 |
* |
| 5 |
* @package Gutenify |
| 6 |
*/ |
| 7 |
|
| 8 |
if ( ! defined( 'ABSPATH' ) ) { |
| 9 |
exit; |
| 10 |
} |
| 11 |
|
| 12 |
if ( ! class_exists( 'woocommerce' ) ) { |
| 13 |
return; |
| 14 |
} |
| 15 |
|
| 16 |
// Only trust context/the current post as the product to show when it |
| 17 |
// actually IS a product — otherwise a page/post that merely happens to |
| 18 |
// contain this block (context always supplies *some* postId once |
| 19 |
// do_blocks() runs on real page/post content, per usesContext) would |
| 20 |
// silently defeat previewProductId, which exists precisely for that |
| 21 |
// non-product-context case (both the editor's ServerSideRender preview |
| 22 |
// and, previously incorrectly, the frontend). |
| 23 |
$context_product_id = 0; |
| 24 |
|
| 25 |
if ( isset( $block->context['postId'] ) && 'product' === get_post_type( absint( $block->context['postId'] ) ) ) { |
| 26 |
$context_product_id = absint( $block->context['postId'] ); |
| 27 |
} |
| 28 |
|
| 29 |
if ( ! $context_product_id && ! empty( $attributes['previewProductId'] ) ) { |
| 30 |
$context_product_id = absint( $attributes['previewProductId'] ); |
| 31 |
} |
| 32 |
|
| 33 |
if ( ! $context_product_id && 'product' === get_post_type() ) { |
| 34 |
$context_product_id = get_the_ID(); |
| 35 |
} |
| 36 |
$gallery_product = wc_get_product( $context_product_id ); |
| 37 |
|
| 38 |
if ( ! $gallery_product ) { |
| 39 |
if ( is_admin() || wp_is_json_request() ) { |
| 40 |
echo '<p class="gutenify--single-product-gallery__message">' . esc_html__( 'The gallery will appear when this block has a WooCommerce product context.', 'gutenify' ) . '</p>'; |
| 41 |
} |
| 42 |
return; |
| 43 |
} |
| 44 |
|
| 45 |
$allowed_layouts = array( 'stack', 'grid-2', 'grid-3', 'feature-grid', 'feature-columns' ); |
| 46 |
$layout = isset( $attributes['layout'] ) && in_array( $attributes['layout'], $allowed_layouts, true ) ? $attributes['layout'] : 'grid-2'; |
| 47 |
// Same preset values as core's Image block "Aspect ratio" control — |
| 48 |
// whitelisted rather than passed through raw since this becomes a CSS |
| 49 |
// custom property value below. |
| 50 |
$allowed_aspect_ratios = array( 'auto', '1', '4/3', '3/2', '16/9', '3/4', '2/3', '9/16' ); |
| 51 |
// Default is "Square" (1), not "Original" (auto) — a real product's own |
| 52 |
// gallery photos are rarely all the same natural size/orientation, so |
| 53 |
// "Original" produced a visibly broken grid out of the box (mismatched |
| 54 |
// image heights, large empty gaps next to a landscape photo). A uniform |
| 55 |
// square crop is the standard e-commerce gallery default for exactly |
| 56 |
// this reason (WooCommerce's own recommended product image size is |
| 57 |
// itself square). |
| 58 |
$aspect_ratio = isset( $attributes['aspectRatio'] ) && in_array( $attributes['aspectRatio'], $allowed_aspect_ratios, true ) ? $attributes['aspectRatio'] : '1'; |
| 59 |
// Same three values as CSS's own `object-fit` keywords, matching core's |
| 60 |
// Image block "Scale" control (Cover/Contain/Fill) 1:1 — no translation |
| 61 |
// layer needed between the attribute and the CSS custom property below. |
| 62 |
$allowed_scales = array( 'cover', 'contain', 'fill' ); |
| 63 |
$scale = isset( $attributes['scale'] ) && in_array( $attributes['scale'], $allowed_scales, true ) ? $attributes['scale'] : 'cover'; |
| 64 |
// `focalPoint` mirrors core's Image block attribute shape ({x, y}, each |
| 65 |
// 0-1) — clamped here since it becomes a CSS percentage below. |
| 66 |
$focal_point = isset( $attributes['focalPoint'] ) && is_array( $attributes['focalPoint'] ) ? $attributes['focalPoint'] : array(); |
| 67 |
$focal_point_x = isset( $focal_point['x'] ) ? min( 1, max( 0, (float) $focal_point['x'] ) ) : 0.5; |
| 68 |
$focal_point_y = isset( $focal_point['y'] ) ? min( 1, max( 0, (float) $focal_point['y'] ) ) : 0.5; |
| 69 |
$enable_lightbox = ! isset( $attributes['enableLightbox'] ) || ! empty( $attributes['enableLightbox'] ); |
| 70 |
$close_on_overlay_click = ! isset( $attributes['closeLightboxOnOverlayClick'] ) || ! empty( $attributes['closeLightboxOnOverlayClick'] ); |
| 71 |
$show_dots = ! isset( $attributes['showDots'] ) || ! empty( $attributes['showDots'] ); |
| 72 |
$show_sale_badge = ! isset( $attributes['showSaleBadge'] ) || ! empty( $attributes['showSaleBadge'] ); |
| 73 |
$allowed_badge_alignments = array( 'left', 'center', 'right' ); |
| 74 |
$sale_badge_align = isset( $attributes['saleBadgeAlign'] ) && in_array( $attributes['saleBadgeAlign'], $allowed_badge_alignments, true ) ? $attributes['saleBadgeAlign'] : 'right'; |
| 75 |
$image_ids = array_values( |
| 76 |
array_filter( |
| 77 |
array_unique( |
| 78 |
array_merge( |
| 79 |
array( absint( $gallery_product->get_image_id() ) ), |
| 80 |
array_map( 'absint', $gallery_product->get_gallery_image_ids() ) |
| 81 |
) |
| 82 |
) |
| 83 |
) |
| 84 |
); |
| 85 |
|
| 86 |
if ( empty( $image_ids ) ) { |
| 87 |
if ( is_admin() || wp_is_json_request() ) { |
| 88 |
echo '<p class="gutenify--single-product-gallery__message">' . esc_html__( 'This product does not have gallery images yet.', 'gutenify' ) . '</p>'; |
| 89 |
} |
| 90 |
return; |
| 91 |
} |
| 92 |
|
| 93 |
$sale_badge = $content; |
| 94 |
if ( $show_sale_badge && '' === trim( $sale_badge ) && $gallery_product->is_on_sale() && class_exists( 'WP_Block' ) ) { |
| 95 |
$sale_badge_block = new WP_Block( |
| 96 |
array( |
| 97 |
'blockName' => 'woocommerce/product-sale-badge', |
| 98 |
'attrs' => array( 'align' => $sale_badge_align ), |
| 99 |
), |
| 100 |
array( 'postId' => $gallery_product->get_id() ) |
| 101 |
); |
| 102 |
$sale_badge = $sale_badge_block->render(); |
| 103 |
} |
| 104 |
|
| 105 |
$wrapper_attributes = get_block_wrapper_attributes( |
| 106 |
array( |
| 107 |
'class' => 'gutenify--single-product-gallery wc-block-components-product-image is-layout-' . $layout, |
| 108 |
'style' => sprintf( |
| 109 |
'--single-product-gallery-aspect-ratio:%1$s;--single-product-gallery-scale:%2$s;--single-product-gallery-focal-x:%3$s%%;--single-product-gallery-focal-y:%4$s%%;', |
| 110 |
esc_attr( $aspect_ratio ), |
| 111 |
esc_attr( $scale ), |
| 112 |
esc_attr( $focal_point_x * 100 ), |
| 113 |
esc_attr( $focal_point_y * 100 ) |
| 114 |
), |
| 115 |
'data-ff-product-gallery' => '', |
| 116 |
'data-ff-lightbox' => $enable_lightbox ? 'true' : 'false', |
| 117 |
'data-ff-lightbox-overlay-close' => $close_on_overlay_click ? 'true' : 'false', |
| 118 |
) |
| 119 |
); |
| 120 |
?> |
| 121 |
<div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>> |
| 122 |
<?php if ( $show_sale_badge ) : ?> |
| 123 |
<?php echo $sale_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 124 |
<?php endif; ?> |
| 125 |
<div class="gutenify--single-product-gallery__viewport"> |
| 126 |
<div class="gutenify--single-product-gallery__track" style="--single-product-gallery-secondary-count:<?php echo esc_attr( max( 1, count( $image_ids ) - 1 ) ); ?>"> |
| 127 |
<?php foreach ( $image_ids as $index => $image_id ) : ?> |
| 128 |
<?php |
| 129 |
$full_image_src = wp_get_attachment_image_src( $image_id, 'full' ); |
| 130 |
$full_image = $full_image_src ? array( |
| 131 |
'src' => $full_image_src[0], |
| 132 |
'width' => $full_image_src[1], |
| 133 |
'height' => $full_image_src[2], |
| 134 |
'alt' => get_post_meta( $image_id, '_wp_attachment_image_alt', true ), |
| 135 |
) : null; |
| 136 |
?> |
| 137 |
<figure class="gutenify--single-product-gallery__item"> |
| 138 |
<?php if ( $enable_lightbox ) : ?> |
| 139 |
<button |
| 140 |
type="button" |
| 141 |
class="gutenify--single-product-gallery__image-button" |
| 142 |
data-ff-gallery-item="<?php echo esc_attr( $index ); ?>" |
| 143 |
data-ff-image-src="<?php echo esc_url( $full_image ? $full_image['src'] : '' ); ?>" |
| 144 |
data-ff-image-alt="<?php echo esc_attr( $full_image ? $full_image['alt'] : '' ); ?>" |
| 145 |
data-ff-image-width="<?php echo esc_attr( $full_image ? $full_image['width'] : 0 ); ?>" |
| 146 |
data-ff-image-height="<?php echo esc_attr( $full_image ? $full_image['height'] : 0 ); ?>" |
| 147 |
aria-label="<?php echo esc_attr( sprintf( __( 'Open product image %1$d of %2$d', 'gutenify' ), $index + 1, count( $image_ids ) ) ); ?>" |
| 148 |
> |
| 149 |
<?php echo wp_get_attachment_image( $image_id, 'woocommerce_single', false, array( 'loading' => 0 === $index ? 'eager' : 'lazy' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 150 |
</button> |
| 151 |
<?php else : ?> |
| 152 |
<?php echo wp_get_attachment_image( $image_id, 'woocommerce_single', false, array( 'loading' => 0 === $index ? 'eager' : 'lazy' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 153 |
<?php endif; ?> |
| 154 |
</figure> |
| 155 |
<?php endforeach; ?> |
| 156 |
</div> |
| 157 |
</div> |
| 158 |
|
| 159 |
<div class="gutenify--single-product-gallery__mobile-ui"> |
| 160 |
<div class="gutenify--single-product-gallery__counter" role="status" aria-live="polite"> |
| 161 |
<span class="screen-reader-text"><?php esc_html_e( 'Image', 'gutenify' ); ?></span> |
| 162 |
<span data-ff-gallery-current>1</span> |
| 163 |
<span aria-hidden="true">/</span> |
| 164 |
<span class="screen-reader-text"><?php esc_html_e( 'of', 'gutenify' ); ?></span> |
| 165 |
<span><?php echo esc_html( count( $image_ids ) ); ?></span> |
| 166 |
</div> |
| 167 |
|
| 168 |
<?php if ( $show_dots && count( $image_ids ) > 1 ) : ?> |
| 169 |
<div class="gutenify--single-product-gallery__dots" role="group" aria-label="<?php esc_attr_e( 'Choose product image', 'gutenify' ); ?>"> |
| 170 |
<?php foreach ( $image_ids as $index => $image_id ) : // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable ?> |
| 171 |
<button |
| 172 |
type="button" |
| 173 |
class="gutenify--single-product-gallery__dot<?php echo 0 === $index ? ' is-active' : ''; ?>" |
| 174 |
data-ff-gallery-dot="<?php echo esc_attr( $index ); ?>" |
| 175 |
aria-label="<?php echo esc_attr( sprintf( __( 'Show image %d', 'gutenify' ), $index + 1 ) ); ?>" |
| 176 |
aria-pressed="<?php echo 0 === $index ? 'true' : 'false'; ?>" |
| 177 |
></button> |
| 178 |
<?php endforeach; ?> |
| 179 |
</div> |
| 180 |
<?php endif; ?> |
| 181 |
</div> |
| 182 |
</div> |
| 183 |
|