PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 11.1.0
WooCommerce v11.1.0
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Blocks / Utils / ProductGalleryUtils.php
woocommerce / src / Blocks / Utils Last commit date
BlockHooksTrait.php 2 months ago BlockTemplateUtils.php 2 weeks ago BlocksSharedState.php 5 months ago BlocksWpQuery.php 2 years ago CartCheckoutUtils.php 2 months ago MiniCartUtils.php 1 year ago ProductAvailabilityUtils.php 1 year ago ProductDataUtils.php 1 year ago ProductGalleryUtils.php 1 week ago StyleAttributesUtils.php 1 year ago Utils.php 1 week ago
ProductGalleryUtils.php
579 lines
1 <?php
2 namespace Automattic\WooCommerce\Blocks\Utils;
3
4 use Automattic\WooCommerce\Internal\ProductGallery\ProductMediaGallery;
5
6 /**
7 * Utility methods used for the Product Gallery block.
8 * {@internal This class and its methods are not intended for public use.}
9 */
10 class ProductGalleryUtils {
11 /**
12 * Get all image IDs for the product.
13 *
14 * @param \WC_Product $product The product object.
15 * @return array An array of image IDs.
16 */
17 public static function get_all_image_ids( $product ) {
18 if ( ! $product instanceof \WC_Product ) {
19 wc_doing_it_wrong( __FUNCTION__, __( 'Invalid product object.', 'woocommerce' ), '9.8.0' );
20 return array();
21 }
22
23 $gallery_image_ids = self::get_product_gallery_image_ids( $product );
24 $product_variation_image_ids = self::get_product_variation_image_ids( $product );
25 $all_image_ids = array_values( array_map( 'intval', array_unique( array_merge( $gallery_image_ids, $product_variation_image_ids ) ) ) );
26
27 if ( empty( $all_image_ids ) ) {
28 return array();
29 }
30
31 return $all_image_ids;
32 }
33
34 /**
35 * Get the product gallery image data.
36 *
37 * @param \WC_Product $product The product object to retrieve the gallery images for.
38 * @param string $size The size of the image to retrieve.
39 * @return array An array of image data for the product gallery.
40 */
41 public static function get_product_gallery_image_data( $product, $size ) {
42 $all_image_ids = self::get_all_image_ids( $product );
43 return self::get_image_src_data( $all_image_ids, $size, $product->get_title() );
44 }
45
46 /**
47 * Get the product gallery image count.
48 *
49 * @deprecated 11.1.0 Use get_product_gallery_media_count instead.
50 *
51 * @param \WC_Product $product The product object to retrieve the gallery images for.
52 * @return int The number of media items in the product gallery.
53 */
54 public static function get_product_gallery_image_count( $product ) {
55 wc_deprecated_function( __METHOD__, '11.1.0', 'get_product_gallery_media_count' );
56
57 return self::get_product_gallery_media_count( $product );
58 }
59
60 /**
61 * Get all media items for the product gallery.
62 *
63 * @param \WC_Product $product The product object.
64 * @return array[] An array of media gallery items.
65 */
66 public static function get_all_media_items( $product ) {
67 if ( ! $product instanceof \WC_Product ) {
68 wc_doing_it_wrong( __FUNCTION__, __( 'Invalid product object.', 'woocommerce' ), '10.9.0' );
69 return array();
70 }
71
72 $product_media_items = ProductMediaGallery::get_product_media_gallery_items_for_display( $product );
73 $product_variation_image_ids = self::get_product_variation_image_ids( $product );
74 $is_placeholder_only = 1 === count( $product_media_items ) && 0 === (int) ( $product_media_items[0]['id'] ?? 0 );
75
76 // If the media gallery only returned the placeholder, fall back to gallery images.
77 if ( $is_placeholder_only && ! empty( $product->get_gallery_image_ids() ) ) {
78 $product_media_items = self::get_product_gallery_image_items( $product );
79 }
80
81 $existing_ids = array_map( 'strval', self::get_media_ids( $product_media_items ) );
82
83 foreach ( $product_variation_image_ids as $variation_image_id ) {
84 if ( in_array( strval( $variation_image_id ), $existing_ids, true ) ) {
85 continue;
86 }
87
88 $product_media_items[] = array(
89 'media_type' => 'image',
90 'source_type' => 'attachment',
91 'id' => absint( $variation_image_id ),
92 );
93 $existing_ids[] = strval( $variation_image_id );
94 }
95
96 return array_values( $product_media_items );
97 }
98
99 /**
100 * Get product gallery media IDs.
101 *
102 * @param array[] $media_items Product gallery media items.
103 * @return int[] Media IDs used by the product gallery frontend store.
104 */
105 public static function get_media_ids( $media_items ) {
106 return array_values(
107 array_map(
108 static function ( $media_item ) {
109 return isset( $media_item['id'] ) ? (int) $media_item['id'] : 0;
110 },
111 $media_items
112 )
113 );
114 }
115
116 /**
117 * Get the product gallery media data.
118 *
119 * @param \WC_Product $product The product object to retrieve the gallery media for.
120 * @param string $size The size of the poster/image to retrieve.
121 * @return array[] An array of media data for the product gallery.
122 */
123 public static function get_product_gallery_media_data( $product, $size ) {
124 $media_items = self::get_all_media_items( $product );
125 return self::get_media_src_data( $media_items, $size, $product->get_title() );
126 }
127
128 /**
129 * Get the product gallery media count.
130 *
131 * @param \WC_Product $product The product object to retrieve the gallery media for.
132 * @return int The number of media items in the product gallery.
133 */
134 public static function get_product_gallery_media_count( $product ) {
135 $media_items = self::get_all_media_items( $product );
136 return count( $media_items );
137 }
138
139 /**
140 * Get the image source data.
141 *
142 * @param array $image_ids The image IDs to retrieve the source data for.
143 * @param string $size The size of the image to retrieve.
144 * @param string $product_title The title of the product used for alt fallback.
145 * @return array An array of image source data.
146 */
147 public static function get_image_src_data( $image_ids, $size, $product_title = '' ) {
148 $image_src_data = array();
149
150 foreach ( $image_ids as $index => $image_id ) {
151 $image_src_data[] = self::get_image_data( $image_id, $size, $product_title, $index );
152 }
153
154 return $image_src_data;
155 }
156
157 /**
158 * Get media source data.
159 *
160 * @param array[] $media_items Product gallery media items.
161 * @param string $size The size of the poster/image to retrieve.
162 * @param string $product_title The title of the product used for alt fallback.
163 * @return array[] An array of media source data.
164 */
165 public static function get_media_src_data( $media_items, $size, $product_title = '' ) {
166 $media_src_data = array();
167
168 foreach ( $media_items as $index => $media_item ) {
169 if ( 'video' === ( $media_item['media_type'] ?? '' ) ) {
170 $media_src_data[] = self::get_video_data( $media_item, $size, $product_title, $index );
171 continue;
172 }
173
174 $media_src_data[] = array_merge(
175 self::get_image_data( $media_item['id'] ?? 0, $size, $product_title, $index ),
176 array( 'media_type' => 'image' )
177 );
178 }
179
180 return $media_src_data;
181 }
182
183 /**
184 * Get base attributes for product gallery videos.
185 *
186 * @param array $media Video media data.
187 * @param string $video_location Video location in the product gallery.
188 * @return array Video attributes.
189 */
190 public static function get_video_attributes( $media, $video_location ) {
191 if ( empty( $media['video_src'] ) ) {
192 return array();
193 }
194
195 $video_location = in_array( $video_location, array( 'dialog', 'gallery' ), true ) ? $video_location : 'gallery';
196 $attrs = array(
197 'aria-label' => $media['alt'] ?? '',
198 'autoplay' => 'autoplay',
199 'data-image-id' => $media['id'] ?? '',
200 'data-wp-context' => sprintf( '{"videoLocation":"%s"}', $video_location ),
201 'data-wp-watch' => 'callbacks.syncVideoPlayback',
202 'loop' => 'loop',
203 'muted' => 'muted',
204 'playsinline' => 'playsinline',
205 'preload' => 'metadata',
206 'src' => $media['video_src'],
207 );
208
209 if ( 'dialog' === $video_location ) {
210 unset( $attrs['autoplay'] );
211 $attrs['data-wp-init--dialog-video-playback'] = 'callbacks.initDialogVideoPlayback';
212 }
213
214 if ( ! empty( $media['poster_id'] ) && ! empty( $media['poster_src'] ) ) {
215 $attrs['poster'] = $media['poster_src'];
216 }
217
218 return $attrs;
219 }
220
221 /**
222 * Get product gallery video HTML from attributes.
223 *
224 * @param array $attributes Video attributes.
225 * @return string Video HTML.
226 */
227 public static function get_video_html( $attributes ) {
228 if ( empty( $attributes ) ) {
229 return '';
230 }
231
232 return '<video ' . wc_implode_html_attributes(
233 array_filter(
234 $attributes,
235 static function ( $value ) {
236 return '' !== $value;
237 }
238 )
239 ) . '></video>';
240 }
241
242 /**
243 * Get source data for one image.
244 *
245 * @param int|string $image_id The image ID.
246 * @param string $size The size of the image to retrieve.
247 * @param string $product_title The title of the product used for alt fallback.
248 * @param int $index The image index.
249 * @return array Image source data.
250 */
251 private static function get_image_data( $image_id, $size, $product_title, $index ) {
252 $image_id = (int) $image_id;
253
254 if ( 0 === $image_id ) {
255 return array(
256 'id' => 0,
257 'media_type' => 'image',
258 'src' => wc_placeholder_img_src(),
259 'srcset' => '',
260 'sizes' => '',
261 'alt' => '',
262 );
263 }
264
265 $full_src = wp_get_attachment_image_src( $image_id, $size );
266 $srcset = wp_get_attachment_image_srcset( $image_id, $size );
267 $sizes = wp_get_attachment_image_sizes( $image_id, $size );
268 $alt = get_post_meta( $image_id, '_wp_attachment_image_alt', true );
269
270 return array(
271 'id' => $image_id,
272 'media_type' => 'image',
273 'src' => $full_src ? $full_src[0] : '',
274 'srcset' => $srcset ? $srcset : '',
275 'sizes' => $sizes ? $sizes : '',
276 'alt' => $alt ? $alt : sprintf(
277 /* translators: 1: Product title 2: Image number */
278 __( '%1$s - Image %2$d', 'woocommerce' ),
279 $product_title,
280 $index + 1
281 ),
282 );
283 }
284
285 /**
286 * Get source data for one video.
287 *
288 * @param array $media_item Product gallery media item.
289 * @param string $size The size of the poster image to retrieve.
290 * @param string $product_title The title of the product used for alt fallback.
291 * @param int $index The media index.
292 * @return array Video source data.
293 */
294 private static function get_video_data( $media_item, $size, $product_title, $index ) {
295 $video_id = isset( $media_item['id'] ) ? (int) $media_item['id'] : 0;
296 $poster_id = ProductMediaGallery::get_video_poster_id( $media_item );
297 $poster = self::get_image_data( $poster_id, $size, $product_title, $index );
298 $video_src = $video_id ? wp_get_attachment_url( $video_id ) : ( $media_item['url'] ?? '' );
299 $mime_type = $video_id ? get_post_mime_type( $video_id ) : '';
300 $settings = isset( $media_item['settings'] ) && is_array( $media_item['settings'] )
301 ? $media_item['settings']
302 : array();
303 $alt = $poster_id ? get_post_meta( $poster_id, '_wp_attachment_image_alt', true ) : '';
304
305 if ( empty( $alt ) && $video_id ) {
306 $video_title = get_post_field( 'post_title', $video_id );
307 $alt = $video_title
308 ? sprintf(
309 /* translators: %s is the video title. */
310 __( 'Video: %s', 'woocommerce' ),
311 $video_title
312 )
313 : '';
314 }
315
316 if ( empty( $alt ) && $product_title ) {
317 $alt = sprintf(
318 /* translators: %s is the product title. */
319 __( 'Product video: %s', 'woocommerce' ),
320 $product_title
321 );
322 }
323
324 return array_merge(
325 $poster,
326 array(
327 'alt' => $alt,
328 'id' => $video_id,
329 'media_type' => 'video',
330 'mime_type' => $mime_type ? $mime_type : '',
331 'poster_id' => $poster_id,
332 'poster_src' => $poster['src'],
333 'settings' => $settings,
334 'video_src' => $video_src ? $video_src : '',
335 )
336 );
337 }
338
339 /**
340 * Get the product gallery image items.
341 *
342 * @param \WC_Product $product The product object to retrieve the gallery images for.
343 * @return array[] An array of image media items for the product gallery.
344 */
345 private static function get_product_gallery_image_items( $product ) {
346 return array_map(
347 static function ( $image_id ) {
348 return array(
349 'media_type' => 'image',
350 'source_type' => 'attachment',
351 'id' => absint( $image_id ),
352 );
353 },
354 self::get_product_gallery_image_ids( $product )
355 );
356 }
357
358 /**
359 * Get the product variation image data.
360 *
361 * @param \WC_Product $product The product object to retrieve the variation images for.
362 * @return array An array of image data for the product variation images.
363 */
364 public static function get_product_variation_image_ids( $product ) {
365 $variation_image_ids = array();
366
367 if ( ! $product instanceof \WC_Product ) {
368 wc_doing_it_wrong( __FUNCTION__, __( 'Invalid product object.', 'woocommerce' ), '9.8.0' );
369 return $variation_image_ids;
370 }
371
372 try {
373 if ( $product->is_type( 'variable' ) ) {
374 $variation_gallery_data = self::get_product_variation_gallery_data( $product );
375
376 foreach ( $variation_gallery_data as $variation_data ) {
377 $variation_image_ids = array_merge( $variation_image_ids, $variation_data['image_ids'] );
378 }
379 }
380 } catch ( \Exception $e ) {
381 // Log the error but continue execution.
382 error_log( 'Error getting product variation image IDs: ' . $e->getMessage() );
383 }
384
385 $unique_int_ids = array_unique( array_map( 'intval', $variation_image_ids ) );
386
387 return array_values( array_map( 'strval', $unique_int_ids ) );
388 }
389
390 /**
391 * Get variation gallery data keyed by variation ID.
392 *
393 * @param \WC_Product $product The product object to retrieve variation gallery data for.
394 * @return array<int, array<string, mixed>> Variation gallery data.
395 */
396 public static function get_product_variation_gallery_data( $product ) {
397 $variation_gallery_data = array();
398
399 if ( ! $product instanceof \WC_Product ) {
400 wc_doing_it_wrong( __FUNCTION__, __( 'Invalid product object.', 'woocommerce' ), '10.8.0' );
401 return $variation_gallery_data;
402 }
403
404 if ( ! $product->is_type( 'variable' ) ) {
405 return $variation_gallery_data;
406 }
407
408 $variations = $product->get_children();
409 if ( ! empty( $variations ) ) {
410 // Bulk-load posts + postmeta into WP's object cache.
411 _prime_post_caches( $variations );
412 }
413
414 // 0 is placeholder image ID.
415 $parent_featured_id = 0;
416 $product_image_id = (int) $product->get_image_id();
417 if ( $product_image_id && wp_attachment_is_image( $product_image_id ) ) {
418 $parent_featured_id = $product_image_id;
419 }
420
421 $parent_gallery_ids = array_map( 'intval', $product->get_gallery_image_ids() );
422 $parent_gallery_ids = array_filter( $parent_gallery_ids, 'wp_attachment_is_image' );
423 $parent_gallery_extras = array_values( array_diff( $parent_gallery_ids, array( $parent_featured_id ) ) );
424
425 foreach ( $variations as $variation_id ) {
426 $variation_id = (int) $variation_id;
427 $entry = self::build_variation_gallery_entry( $variation_id, $parent_featured_id, $parent_gallery_extras );
428
429 if ( null !== $entry ) {
430 $variation_gallery_data[ $variation_id ] = $entry;
431 }
432 }
433
434 return $variation_gallery_data;
435 }
436
437 /**
438 * Build the gallery payload for a single variation, or null when the
439 * post isn't a real variation.
440 *
441 * Decision tree (variation chosen):
442 * - no variation images → parent featured + parent gallery
443 * - own featured only → variation featured + parent gallery extras
444 * - own featured + gallery → variation images only
445 * - gallery only, no own featured (potential AVI shape) → parent featured + variation gallery
446 *
447 * @param int $variation_id Variation post ID.
448 * @param int $parent_featured_id Parent product's featured image ID (0 if missing/invalid).
449 * @param int[] $parent_gallery_extras Parent gallery image IDs, with the featured filtered out.
450 * @return array<string, mixed>|null
451 */
452 private static function build_variation_gallery_entry( int $variation_id, int $parent_featured_id, array $parent_gallery_extras ): ?array {
453 $variation = wc_get_product( $variation_id );
454
455 if ( ! $variation instanceof \WC_Product_Variation ) {
456 return null;
457 }
458
459 $featured_id = (int) $variation->get_image_id();
460 $featured_valid = $featured_id && wp_attachment_is_image( $featured_id );
461
462 $variation_gallery_ids = array_map( 'intval', $variation->get_gallery_image_ids() );
463 $variation_gallery_ids = array_filter( $variation_gallery_ids, 'wp_attachment_is_image' );
464 $variation_gallery_ids = array_values( $variation_gallery_ids );
465
466 // No images from variation - full parent fallback.
467 if ( ! $featured_valid && empty( $variation_gallery_ids ) ) {
468 $parent_image_ids = array_values(
469 array_filter( array_merge( array( $parent_featured_id ), $parent_gallery_extras ) )
470 );
471
472 if ( empty( $parent_image_ids ) ) {
473 return array(
474 'image_id' => 0,
475 'image_ids' => array( 0 ),
476 );
477 }
478
479 return array(
480 'image_id' => $parent_image_ids[0],
481 'image_ids' => $parent_image_ids,
482 );
483 }
484
485 // Variation has featured image and gallery - full variation gallery.
486 if ( ! empty( $variation_gallery_ids ) ) {
487 $featured = $featured_valid ? $featured_id : $variation_gallery_ids[0];
488 $image_ids = array_values(
489 array_unique( array_merge( array( $featured ), $variation_gallery_ids ) )
490 );
491
492 return array(
493 'image_id' => $featured,
494 'image_ids' => $image_ids,
495 );
496 }
497
498 // Variation has only featured image - variation featured and parent gallery.
499 $image_ids = array_values(
500 array_unique( array_merge( array( $featured_id ), $parent_gallery_extras ) )
501 );
502
503 return array(
504 'image_id' => $featured_id,
505 'image_ids' => $image_ids,
506 );
507 }
508
509 /**
510 * Get all image IDs relevant to a variation gallery.
511 *
512 * @param \WC_Product_Variation $variation The variation object.
513 * @return array<int> Variation image IDs.
514 */
515 public static function get_variation_gallery_image_ids( \WC_Product_Variation $variation ) {
516 $image_ids = array();
517 $variation_image_id = (int) $variation->get_image_id();
518 $gallery_image_ids = array_map( 'intval', $variation->get_gallery_image_ids() );
519
520 if ( $variation_image_id ) {
521 $image_ids[] = $variation_image_id;
522 }
523
524 if ( ! empty( $gallery_image_ids ) ) {
525 $image_ids = array_merge( $image_ids, $gallery_image_ids );
526 }
527
528 // Filter out missing/invalid attachments to avoid rendering phantom
529 // empty `<li>` wrappers that the visibility watch can't manage.
530 $image_ids = array_filter(
531 $image_ids,
532 function ( $id ) {
533 return $id > 0 && wp_attachment_is_image( $id );
534 }
535 );
536
537 return array_values( array_unique( $image_ids ) );
538 }
539
540 /**
541 * Get the product gallery image IDs.
542 *
543 * @param \WC_Product $product The product object to retrieve the gallery images for.
544 * @return array An array of unique image IDs for the product gallery.
545 */
546 public static function get_product_gallery_image_ids( $product ) {
547 $product_image_ids = array();
548
549 // Main product featured image.
550 $featured_image_id = $product->get_image_id();
551
552 if ( $featured_image_id ) {
553 $product_image_ids[] = $featured_image_id;
554 }
555
556 // All other product gallery images.
557 $product_gallery_image_ids = $product->get_gallery_image_ids();
558
559 if ( ! empty( $product_gallery_image_ids ) ) {
560 // We don't want to show the same image twice, so we have to remove the featured image from the gallery if it's there.
561 $product_image_ids = array_unique( array_merge( $product_image_ids, $product_gallery_image_ids ) );
562 }
563
564 // If the Product image is not set and there are no gallery images, we need to set it to a placeholder image.
565 if ( ! $featured_image_id && empty( $product_gallery_image_ids ) ) {
566 $product_image_ids[] = '0';
567 }
568
569 foreach ( $product_image_ids as $key => $image_id ) {
570 $product_image_ids[ $key ] = strval( $image_id );
571 }
572
573 // Reindex array.
574 $product_image_ids = array_values( $product_image_ids );
575
576 return $product_image_ids;
577 }
578 }
579