PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.3.0
ShopBuilder – WooCommerce Builder For Elementor v3.3.0
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
shopbuilder / app / Modules / VariationGallery / GalleryFns.php

GalleryFns.php in ShopBuilder – WooCommerce Builder For Elementor 3.3.0, at app/Modules/VariationGallery/GalleryFns.php

481 lines 16.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sticky add-to-cart Functions Class.
4 *
5 * @package Rse\SB
6 */
7
8 namespace RadiusTheme\SB\Modules\VariationGallery;
9
10 use RadiusTheme\SB\Helpers\Cache;
11 use RadiusTheme\SB\Helpers\Fns;
12 use WC_Product;
13
14 defined( 'ABSPATH' ) || exit();
15
16 /**
17 * Sticky add-to-cart Functions Class.
18 */
19 class GalleryFns {
20 /**
21 * @param string $key Default Attribute.
22 * @param array|string $default Default Attribute.
23 * @return array|string
24 */
25 public static function get_options( $key = null, $default = '' ) {
26 $options = Fns::get_options( 'modules', 'variation_gallery' );
27 if ( $key ) {
28 if ( isset( $options[ $key ] ) ) {
29 return $options[ $key ];
30 } else {
31 return $default;
32 }
33 }
34 return $options;
35 }
36
37 /**
38 * Output the HTML image tag for a product attachment.
39 *
40 * Retrieves the image properties using `product_attachment_props()` and outputs
41 * the image using `wp_get_attachment_image()`. Intended for variation galleries
42 * or product thumbnails.
43 *
44 * @param int $image_id The attachment/image ID.
45 * @param \WC_Product $product The WooCommerce product object.
46 * @param string $size The image size to output. Default 'woocommerce_thumbnail'.
47 *
48 * @return string The HTML.
49 */
50 public static function get_main_attachment_html( $image_id, $product, $size = 'woocommerce_thumbnail' ) {
51 $props = self::product_attachment_props( $image_id, $product );
52 $html = self::wc_get_gallery_image_html( $image_id, true );
53 return apply_filters( 'rtsb/vg/get/main/attachment/html', $html, $props, $image_id, $product, $size );
54 }
55
56 /**
57 * Get HTML for a gallery image.
58 *
59 * Hooks: woocommerce_gallery_thumbnail_size, woocommerce_gallery_image_size and woocommerce_gallery_full_size accept name based image sizes, or an array of width/height values.
60 *
61 * @since 3.3.2
62 * @param int $attachment_id Attachment ID.
63 * @param bool $main_image Is this the main image or a thumbnail?.
64 * @param int $image_index The image index in the gallery.
65 * @return string
66 */
67 public static function wc_get_gallery_image_html( $attachment_id, $main_image = false, $image_index = -1 ) {
68 global $product;
69
70 $flexslider = (bool) apply_filters( 'woocommerce_single_product_flexslider_enabled', get_theme_support( 'wc-product-gallery-slider' ) );
71 $gallery_thumbnail = wc_get_image_size( 'gallery_thumbnail' );
72 $thumbnail_size = apply_filters( 'woocommerce_gallery_thumbnail_size', [ $gallery_thumbnail['width'], $gallery_thumbnail['height'] ] );
73 $image_size = apply_filters( 'woocommerce_gallery_image_size', $flexslider || $main_image ? 'woocommerce_single' : $thumbnail_size );
74 $full_size = apply_filters( 'woocommerce_gallery_full_size', apply_filters( 'woocommerce_product_thumbnails_large_size', 'full' ) );
75 $thumbnail_src = wp_get_attachment_image_src( $attachment_id, $thumbnail_size );
76 $thumbnail_srcset = wp_get_attachment_image_srcset( $attachment_id, $thumbnail_size );
77 $thumbnail_sizes = wp_get_attachment_image_sizes( $attachment_id, $thumbnail_size );
78 $full_src = wp_get_attachment_image_src( $attachment_id, $full_size );
79 $alt_text = trim( wp_strip_all_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) );
80 $alt_text = ( empty( $alt_text ) && ( $product instanceof WC_Product ) ) ? woocommerce_get_alt_from_product_title_and_position( $product->get_title(), $main_image, $image_index ) : $alt_text;
81
82 if ( ! is_array( $full_src ) ) {
83 $full_src = [ '', '', '' ];
84 }
85
86 /**
87 * Filters the attributes for the image markup.
88 *
89 * @since 3.3.2
90 *
91 * @param array $image_attributes Attributes for the image markup.
92 */
93 $image_params = apply_filters(
94 'woocommerce_gallery_image_html_attachment_image_params',
95 [
96 'title' => _wp_specialchars( get_post_field( 'post_title', $attachment_id ), ENT_QUOTES, 'UTF-8', true ),
97 'data-caption' => _wp_specialchars( get_post_field( 'post_excerpt', $attachment_id ), ENT_QUOTES, 'UTF-8', true ),
98 'data-src' => esc_url( $full_src[0] ),
99 'data-large_image' => esc_url( $full_src[0] ),
100 'data-large_image_width' => esc_attr( $full_src[1] ),
101 'data-large_image_height' => esc_attr( $full_src[2] ),
102 'class' => esc_attr( $main_image ? 'wp-post-image' : '' ),
103 'alt' => esc_attr( $alt_text ),
104 ],
105 $attachment_id,
106 $image_size,
107 $main_image
108 );
109
110 if ( isset( $image_params['title'] ) ) {
111 unset( $image_params['title'] );
112 }
113
114 $image = wp_get_attachment_image(
115 $attachment_id,
116 $image_size,
117 false,
118 $image_params
119 );
120
121 return $image;
122 }
123
124 /**
125 * Get the HTML for a variation gallery thumbnail image.
126 *
127 * Wraps the image in a container div and optionally adds a video-specific class
128 * if a video link is associated with the image. The final HTML is passed through
129 * a filter for customization.
130 *
131 * @param int $image_id The attachment/image ID.
132 * @param \WC_Product $product The WooCommerce product object.
133 * @param string $size The image size to output. Default 'thumbnail'.
134 *
135 * @return string Filtered HTML for the thumbnail image.
136 */
137 public static function get_thumbnail_attachment_html( $image_id, $product, $size = 'thumbnail' ) {
138 $props = self::product_attachment_props( $image_id, $product );
139 $add_video_class = ! empty( $props['rtsb_vg_video_link'] ) ? ' rtsb-vs-video' : '';
140 ob_start();
141 ?>
142 <div class="rtsb-vs-thumb-item <?php echo esc_attr( $add_video_class ); ?>">
143 <?php Fns::print_html( wp_get_attachment_image( $image_id, $size ), true ); ?>
144 </div>
145 <?php
146 $html = ob_get_clean();
147 return apply_filters( 'rtsb/vg/get/thumbnail/attachment/html', $html, $props, $image_id, $product, $size );
148 }
149 /**
150 * @param int $image_id image id.
151 * @param object $product WC_Product Object.
152 * @return array
153 */
154 public static function product_attachment_props( $image_id, $product ) {
155 $props = wc_get_product_attachment_props( $image_id, $product );
156 $props['image_id'] = $image_id;
157 return apply_filters( 'rtsb/product/attachment/props', $props, $image_id, $product );
158 }
159 /**
160 * @param int $product_id product id.
161 *
162 * @return mixed|void
163 */
164 public static function get_gallery_images_and_props( $product_id ) {
165 $transient_name = self::get_transient_name( $product_id, 'default-images' );
166 $images = get_transient( $transient_name );
167 if ( false !== $images ) {
168 return apply_filters( 'rtsb/vg/get/gallery/images', $images, $product_id );
169 }
170 $product = wc_get_product( $product_id );
171 $product_id = $product->get_id();
172 $attachment_ids = $product->get_gallery_image_ids();
173 $post_thumbnail_id = $product->get_image_id();
174 $images = [];
175 $post_thumbnail_id = (int) apply_filters( 'rtsb/vg/post/thumbnail/id', $post_thumbnail_id, $attachment_ids, $product );
176 $attachment_ids = (array) apply_filters( 'rtsb/vg/attachment/ids', $attachment_ids, $post_thumbnail_id, $product );
177 if ( ! empty( $post_thumbnail_id ) ) {
178 array_unshift( $attachment_ids, $post_thumbnail_id );
179 }
180 if ( is_array( $attachment_ids ) && ! empty( $attachment_ids ) ) {
181 foreach ( $attachment_ids as $i => $image_id ) {
182 if ( $image_id ) {
183 $images[ $i ] = self::product_attachment_props( $image_id, $product );
184 }
185 }
186 }
187 set_transient( $transient_name, $images, 12 * HOUR_IN_SECONDS );
188 Cache::set_transient_cache_key( $transient_name );
189 return apply_filters( 'rtsb/vg/get/gallery/images', $images, $product_id );
190 }
191 /**
192 * Build (and cache) the gallery image props for a single variation.
193 *
194 * Assembles the image-ID list for one variation only — the per-variation logic
195 * previously executed for every variation inside the
196 * `woocommerce_available_variation` filter — so galleries can be fetched
197 * on-demand over AJAX instead of being built in bulk on page load.
198 *
199 * The result is cached in a transient keyed by variation ID and is always a
200 * sequential array (built with `$images[]`) so it JSON-encodes to a JS array.
201 *
202 * @param int $product_id Parent (variable) product ID.
203 * @param int $variation_id Variation ID whose gallery should be built.
204 *
205 * @return array Sequential array of image prop arrays.
206 */
207 public static function get_variation_gallery( $product_id, $variation_id ) {
208 $variation_id = absint( $variation_id );
209 $product_id = absint( $product_id );
210
211 $transient_name = self::get_transient_name( $variation_id, 'variation-images' );
212 $cached = $transient_name ? get_transient( $transient_name ) : false;
213 if ( ! empty( $cached ) && is_array( $cached ) ) {
214 return apply_filters( 'rtsb/vg/get/variation/gallery', $cached, $product_id, $variation_id );
215 }
216
217 $variation = wc_get_product( $variation_id );
218 if ( ! $variation ) {
219 return [];
220 }
221
222 $parent_product = $product_id ? wc_get_product( $product_id ) : false;
223
224 $has_variation_gallery_images = (bool) get_post_meta( $variation_id, 'rtsb_vg_images', true );
225 if ( $has_variation_gallery_images ) {
226 $gallery_images = (array) get_post_meta( $variation_id, 'rtsb_vg_images', true );
227 } elseif ( $parent_product ) {
228 $gallery_images = (array) $parent_product->get_gallery_image_ids();
229 } else {
230 $gallery_images = (array) $variation->get_gallery_image_ids();
231 }
232
233 $variation_image_id = absint( $variation->get_image_id() );
234 if ( ! empty( $variation_image_id ) ) {
235 array_unshift( $gallery_images, $variation_image_id );
236 }
237
238 $gallery_images = array_values( array_unique( array_filter( array_map( 'absint', $gallery_images ) ) ) );
239
240 $images = [];
241 foreach ( $gallery_images as $image_id ) {
242 if ( $image_id ) {
243 // Always append so the result stays a sequential (JS) array.
244 $images[] = self::product_attachment_props( $image_id, $variation );
245 }
246 }
247
248 if ( $transient_name ) {
249 set_transient( $transient_name, $images, HOUR_IN_SECONDS * 12 );
250 Cache::set_transient_cache_key( $transient_name );
251 }
252
253 return apply_filters( 'rtsb/vg/get/variation/gallery', $images, $product_id, $variation_id );
254 }
255
256 /**
257 * Resolve the default variation ID for a variable product.
258 *
259 * Returns the variation that WooCommerce's default attributes fully resolve to,
260 * so the matching gallery can be rendered server-side on initial page load.
261 *
262 * @param \WC_Product $product Variable product object.
263 *
264 * @return int Variation ID, or 0 when no single default variation resolves.
265 */
266 public static function get_default_variation_id( $product ) {
267 if ( ! ( $product instanceof WC_Product ) || ! $product->is_type( 'variable' ) ) {
268 return 0;
269 }
270
271 $default_attributes = $product->get_default_attributes();
272 if ( empty( $default_attributes ) ) {
273 return 0;
274 }
275
276 $match_attributes = [];
277 foreach ( $default_attributes as $key => $value ) {
278 $match_attributes[ 'attribute_' . $key ] = $value;
279 }
280
281 $data_store = \WC_Data_Store::load( 'product' );
282 $variation_id = $data_store->find_matching_product_variation( $product, $match_attributes );
283
284 return absint( $variation_id );
285 }
286
287 /**
288 * Get the attachment IDs to render for a product's initial gallery paint.
289 *
290 * When a default variation resolves, returns that variation's gallery image IDs
291 * (and warms its transient as a side effect of get_variation_gallery()). Otherwise
292 * falls back to the parent product's featured + gallery images.
293 *
294 * @param \WC_Product $product Product object.
295 *
296 * @return array {
297 * @type int[] $image_ids Sequential attachment IDs to render.
298 * @type \WC_Product $context Product/variation object to use as render context.
299 * }
300 */
301 public static function get_initial_gallery_render( $product ) {
302 $context = $product;
303 $image_ids = [];
304
305 if ( $product instanceof WC_Product ) {
306 $default_variation_id = self::get_default_variation_id( $product );
307 if ( $default_variation_id ) {
308 $variation_props = self::get_variation_gallery( $product->get_id(), $default_variation_id );
309 foreach ( (array) $variation_props as $prop ) {
310 if ( ! empty( $prop['image_id'] ) ) {
311 $image_ids[] = absint( $prop['image_id'] );
312 }
313 }
314 if ( ! empty( $image_ids ) ) {
315 $variation = wc_get_product( $default_variation_id );
316 if ( $variation ) {
317 $context = $variation;
318 }
319 }
320 }
321 }
322
323 if ( empty( $image_ids ) && ( $product instanceof WC_Product ) ) {
324 $post_thumbnail_id = $product->get_image_id();
325 $attachment_ids = $product->get_gallery_image_ids();
326 if ( $post_thumbnail_id ) {
327 $image_ids[] = absint( $post_thumbnail_id );
328 }
329 if ( is_array( $attachment_ids ) && count( $attachment_ids ) ) {
330 foreach ( $attachment_ids as $attachment_id ) {
331 $image_ids[] = absint( $attachment_id );
332 }
333 }
334 }
335
336 $image_ids = array_values( array_unique( array_filter( $image_ids ) ) );
337
338 return [
339 'image_ids' => $image_ids,
340 'context' => $context,
341 ];
342 }
343
344 /**
345 * Flush all gallery transients (default + every variation) for a product.
346 *
347 * Used when a product or one of its variations is saved so stale gallery
348 * props are not served from cache.
349 *
350 * @param int $product_id Parent product ID.
351 *
352 * @return void
353 */
354 public static function flush_product_gallery_transients( $product_id ) {
355 $product_id = absint( $product_id );
356 if ( ! $product_id ) {
357 return;
358 }
359
360 self::delete_transients( $product_id, 'default-images' );
361
362 $product = wc_get_product( $product_id );
363 if ( ! $product || ! $product->is_type( 'variable' ) ) {
364 return;
365 }
366
367 foreach ( $product->get_children() as $variation_id ) {
368 self::delete_transients( $variation_id, 'variation-images' );
369 }
370 }
371
372 /**
373 * Flush variation gallery transients that reference a given attachment.
374 *
375 * Invoked when an attachment is deleted so variations whose galleries include
376 * the removed image rebuild their props on next request.
377 *
378 * @param int $attachment_id Deleted attachment ID.
379 *
380 * @return void
381 */
382 public static function flush_attachment_gallery_transients( $attachment_id ) {
383 global $wpdb;
384
385 $attachment_id = absint( $attachment_id );
386 if ( ! $attachment_id ) {
387 return;
388 }
389
390 // Flush the owning product (featured/parent) galleries when determinable.
391 $parent_id = wp_get_post_parent_id( $attachment_id );
392 if ( $parent_id ) {
393 self::flush_product_gallery_transients( $parent_id );
394 }
395
396 // Flush any variation whose custom gallery meta references this attachment.
397 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
398 $rows = $wpdb->get_results(
399 $wpdb->prepare(
400 "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s",
401 'rtsb_vg_images'
402 )
403 );
404
405 if ( empty( $rows ) ) {
406 return;
407 }
408
409 foreach ( $rows as $row ) {
410 $image_ids = maybe_unserialize( $row->meta_value );
411 if ( ! is_array( $image_ids ) ) {
412 continue;
413 }
414 $image_ids = array_map( 'absint', $image_ids );
415 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
416 if ( in_array( $attachment_id, $image_ids, true ) ) {
417 self::delete_transients( $row->post_id, 'variation-images' );
418 }
419 }
420 }
421
422 /**
423 * Helper: Get all images transient name for specific variation/product
424 *
425 * @param int $id unique id.
426 * @param string $type transient type.
427 *
428 * @return string
429 */
430 public static function get_transient_name( $id, $type ) {
431 if ( 'default-images' === $type ) {
432 $id = self::wpml_get_original_variation_id( $id );
433 $transient_name = sprintf( 'rtsb_vg_default_images_%d', $id );
434 } elseif ( 'variation-images' === $type ) {
435 $id = self::wpml_get_original_variation_id( $id );
436 $transient_name = sprintf( 'rtsb_vg_variation_images_%d', $id );
437 } elseif ( 'sizes' === $type ) {
438 $transient_name = sprintf( 'rtsb_vg_variation_image_sizes_%d', $id );
439 } elseif ( 'variation' === $type ) {
440 $transient_name = sprintf( 'rtsb_vg_variation_%d', $id );
441 } else {
442 $transient_name = false;
443 }
444 return apply_filters( 'rtsb/vg/transient/name', $transient_name, $type, $id );
445 }
446 /**
447 * Helper: Delete all transient
448 *
449 * @param int $product_id Product id.
450 * @param string $type Transient type.
451 */
452 public static function delete_transients( $product_id = false, $type = '' ) {
453 if ( $product_id ) {
454 if ( $type ) {
455 $default_transient_name = self::get_transient_name( $product_id, $type );
456 delete_transient( $default_transient_name );
457 } else {
458 $default_transient_name = self::get_transient_name( $product_id, 'default-images' );
459 delete_transient( $default_transient_name );
460 }
461 }
462 }
463
464 /**
465 * Helper: WPML - Get original variation ID
466 *
467 * If WPML is active and this is a translated variaition, get the original ID.
468 *
469 * @param int $id object id.
470 *
471 * @return int
472 */
473 public static function wpml_get_original_variation_id( $id ) {
474 $wpml_original_variation_id = get_post_meta( $id, '_wcml_duplicate_of_variation', true );
475 if ( $wpml_original_variation_id ) {
476 $id = $wpml_original_variation_id;
477 }
478 return $id;
479 }
480 }
481