| @@ -32,60 +32,10 @@ | ||
| 32 | 32 | add_action( 'woocommerce_product_after_variable_attributes', [ $this, 'gallery_admin_html' ], 10, 3 ); |
| 33 | 33 | add_action( 'woocommerce_save_product_variation', [ $this, 'save_variation_gallery' ] ); |
| 34 | 34 | add_action( 'add_meta_boxes', [ $this, 'add_metabox' ] ); |
| 35 | 35 | add_action( 'save_post', [ $this, 'save_metabox' ] ); |
| 36 | - | |
| 37 | - // Late enough that WooCommerce's variation gallery package has registered its | |
| 38 | - // own field, so the duplicate control can be taken back off. | |
| 39 | - add_action( 'admin_init', [ $this, 'remove_native_variation_gallery_field' ], 20 ); | |
| 40 | - | |
| 41 | - add_filter( 'woocommerce_product_export_meta_value', [ $this, 'product_export_meta_value' ], 15, 3 ); | |
| 42 | - | |
| 43 | - // Invalidate cached variation galleries when product data or images change. | |
| 44 | - add_action( 'woocommerce_update_product', [ $this, 'flush_product_gallery_cache' ] ); | |
| 45 | - add_action( 'woocommerce_save_product_variation', [ $this, 'flush_variation_parent_cache' ] ); | |
| 46 | - add_action( 'delete_attachment', [ $this, 'flush_attachment_gallery_cache' ] ); | |
| 47 | 36 | } |
| 48 | - | |
| 49 | 37 | /** |
| 50 | - * Flush all cached gallery transients for a saved product. | |
| 51 | - * | |
| 52 | - * @param int $product_id Product ID. | |
| 53 | - * | |
| 54 | - * @return void | |
| 55 | - */ | |
| 56 | - public function flush_product_gallery_cache( $product_id ) { | |
| 57 | - GalleryFns::flush_product_gallery_transients( $product_id ); | |
| 58 | - } | |
| 59 | - | |
| 60 | - /** | |
| 61 | - * Flush the parent product default-gallery transient when a variation is saved. | |
| 62 | - * | |
| 63 | - * The variation's own transient is already cleared in save_variation_gallery(); | |
| 64 | - * this also clears the parent product default-images cache. | |
| 65 | - * | |
| 66 | - * @param int $variation_id Variation ID. | |
| 67 | - * | |
| 68 | - * @return void | |
| 69 | - */ | |
| 70 | - public function flush_variation_parent_cache( $variation_id ) { | |
| 71 | - $parent_id = wp_get_post_parent_id( $variation_id ); | |
| 72 | - if ( $parent_id ) { | |
| 73 | - GalleryFns::delete_transients( $parent_id, 'default-images' ); | |
| 74 | - } | |
| 75 | - } | |
| 76 | - | |
| 77 | - /** | |
| 78 | - * Flush cached variation galleries referencing a deleted attachment. | |
| 79 | - * | |
| 80 | - * @param int $attachment_id Attachment ID being deleted. | |
| 81 | - * | |
| 82 | - * @return void | |
| 83 | - */ | |
| 84 | - public function flush_attachment_gallery_cache( $attachment_id ) { | |
| 85 | - GalleryFns::flush_attachment_gallery_transients( $attachment_id ); | |
| 86 | - } | |
| 87 | - /** | |
| 88 | 38 | * Adds a custom meta box to the WooCommerce product edit page. |
| 89 | 39 | */ |
| 90 | 40 | public function add_metabox() { |
| 91 | 41 | add_meta_box( |
| @@ -150,116 +100,21 @@ | ||
| 150 | 100 | // Check permissions again and make sure we have what we need. |
| 151 | 101 | if ( ! current_user_can( 'edit_products' ) || empty( $_POST ) || empty( $_POST['product_id'] ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown |
| 152 | 102 | wp_die( -1 ); |
| 153 | 103 | } |
| 154 | - | |
| 155 | - $rtsb_vg_ids = []; | |
| 156 | - | |
| 157 | - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified by check_ajax_referer() above. | |
| 104 | + // Sanitize and save or delete meta. | |
| 158 | 105 | if ( isset( $_POST['rtsb_vg'][ $variation_id ] ) && is_array( $_POST['rtsb_vg'][ $variation_id ] ) ) { |
| 159 | - // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified by check_ajax_referer() above. | |
| 160 | - $rtsb_vg_ids = array_map( 'absint', wp_unslash( $_POST['rtsb_vg'][ $variation_id ] ) ); | |
| 161 | - $rtsb_vg_ids = array_values( array_unique( array_filter( $rtsb_vg_ids ) ) ); | |
| 162 | - } | |
| 163 | - | |
| 164 | - if ( GalleryFns::use_native_gallery() ) { | |
| 165 | - $variation = wc_get_product( $variation_id ); | |
| 166 | - | |
| 167 | - if ( $variation && $variation->is_type( 'variation' ) ) { | |
| 168 | - $variation->set_gallery_image_ids( $rtsb_vg_ids ); | |
| 169 | - $variation->save(); | |
| 170 | - | |
| 171 | - // Stamped only once the native gallery has actually been written: the | |
| 172 | - // sentinel permanently suppresses the legacy fallback, so setting it | |
| 173 | - // after a failed load would strand that variation's images unreachable. | |
| 174 | - GalleryFns::mark_native_gallery_owned( $variation_id ); | |
| 175 | - } | |
| 176 | - } elseif ( $rtsb_vg_ids ) { | |
| 177 | - update_post_meta( $variation_id, GalleryFns::LEGACY_GALLERY_META_KEY, $rtsb_vg_ids ); | |
| 106 | + $rtsb_vg_ids = array_map( 'absint', $_POST['rtsb_vg'][ $variation_id ] ); | |
| 107 | + $rtsb_vg_ids = array_values( array_unique( $rtsb_vg_ids ) ); | |
| 108 | + update_post_meta( $variation_id, 'rtsb_vg_images', $rtsb_vg_ids ); | |
| 178 | 109 | } else { |
| 179 | - delete_post_meta( $variation_id, GalleryFns::LEGACY_GALLERY_META_KEY ); | |
| 110 | + delete_post_meta( $variation_id, 'rtsb_vg_images' ); | |
| 180 | 111 | } |
| 181 | - | |
| 182 | 112 | GalleryFns::delete_transients( $variation_id, 'variation-images' ); |
| 183 | 113 | } |
| 184 | - | |
| 185 | 114 | /** |
| 186 | - * Keep the stale legacy gallery meta out of CSV exports. | |
| 187 | - * | |
| 188 | - * Once a variation is native-owned the legacy meta is a snapshot, not the truth: | |
| 189 | - * the migration copies rather than moves it, and later saves only touch the native | |
| 190 | - * store. Exporting it would put outdated data in the CSV, and re-importing that | |
| 191 | - * file could write it back over a gallery the merchant has since changed. | |
| 192 | - * | |
| 193 | - * WooCommerce exports the native gallery itself through the standard `Images` | |
| 194 | - * column, so nothing is lost by omitting this key. | |
| 195 | - * | |
| 196 | - * @param mixed $meta_value Meta value being exported. | |
| 197 | - * @param object $meta Meta object. | |
| 198 | - * @param \WC_Product $product Product or variation being exported. | |
| 199 | - * | |
| 200 | - * @return mixed | |
| 201 | - */ | |
| 202 | - public function product_export_meta_value( $meta_value, $meta, $product ) { | |
| 203 | - if ( ! GalleryFns::use_native_gallery() ) { | |
| 204 | - return $meta_value; | |
| 205 | - } | |
| 206 | - | |
| 207 | - if ( ! isset( $meta->key ) || GalleryFns::LEGACY_GALLERY_META_KEY !== $meta->key ) { | |
| 208 | - return $meta_value; | |
| 209 | - } | |
| 210 | - | |
| 211 | - if ( $product instanceof \WC_Product && GalleryFns::is_native_gallery_owned( $product->get_id() ) ) { | |
| 212 | - return ''; | |
| 213 | - } | |
| 214 | - | |
| 215 | - return $meta_value; | |
| 216 | - } | |
| 217 | - | |
| 218 | - /** | |
| 219 | - * Hide WooCommerce's own variation gallery field in the product editor. | |
| 220 | - * | |
| 221 | - * This module keeps its existing metabox as the single authoring UI and writes | |
| 222 | - * straight into the native store, so rendering both fields would give merchants two | |
| 223 | - * controls for one value. WooCommerce's field markup is also what its admin CSS | |
| 224 | - * keys off (`:has(.wc-variation-gallery-field)`), so removing the render leaves the | |
| 225 | - * stock variation image slot untouched and the stylesheet inert. | |
| 226 | - * | |
| 227 | - * Nothing is suppressed when this module is inactive — that code path never runs, | |
| 228 | - * so WooCommerce keeps full ownership of its own UI. | |
| 229 | - * | |
| 230 | 115 | * @return void |
| 231 | 116 | */ |
| 232 | - public function remove_native_variation_gallery_field() { | |
| 233 | - $class = 'Automattic\WooCommerce\Internal\VariationGallery\ClassicVariationGalleryAdmin'; | |
| 234 | - $hook = 'woocommerce_variation_after_upload_image'; | |
| 235 | - | |
| 236 | - if ( ! GalleryFns::use_native_gallery() || ! class_exists( $class ) ) { | |
| 237 | - return; | |
| 238 | - } | |
| 239 | - | |
| 240 | - if ( ! apply_filters( 'rtsb/vg/remove/native/variation/gallery/field', true ) ) { | |
| 241 | - return; | |
| 242 | - } | |
| 243 | - | |
| 244 | - if ( empty( $GLOBALS['wp_filter'][ $hook ] ) ) { | |
| 245 | - return; | |
| 246 | - } | |
| 247 | - | |
| 248 | - // Matched on the registered object rather than resolved from WooCommerce's | |
| 249 | - // container, so removal does not depend on the container handing back the very | |
| 250 | - // same instance it registered the callback with. | |
| 251 | - foreach ( $GLOBALS['wp_filter'][ $hook ]->callbacks as $priority => $callbacks ) { | |
| 252 | - foreach ( $callbacks as $callback ) { | |
| 253 | - if ( is_array( $callback['function'] ) && isset( $callback['function'][0] ) && $callback['function'][0] instanceof $class ) { | |
| 254 | - remove_action( $hook, $callback['function'], $priority ); | |
| 255 | - } | |
| 256 | - } | |
| 257 | - } | |
| 258 | - } | |
| 259 | - /** | |
| 260 | - * @return void | |
| 261 | - */ | |
| 262 | 117 | public function admin_template_js() { |
| 263 | 118 | require_once RTSB_PATH . '/app/Modules/VariationGallery/view/template-admin-thumbnail.php'; |
| 264 | 119 | } |
| 265 | 120 | |
| @@ -277,42 +132,11 @@ | ||
| 277 | 132 | * @return void |
| 278 | 133 | */ |
| 279 | 134 | public function gallery_admin_html( $loop, $variation_data, $variation ) { |
| 280 | 135 | $variation_id = absint( $variation->ID ); |
| 281 | - $gallery_images = null; | |
| 282 | - | |
| 283 | - // With the native gallery available it is authoritative, so the metabox shows it | |
| 284 | - // rather than the legacy meta it mirrors — and never reads that meta, which on a | |
| 285 | - // migrated store may no longer exist. The legacy value is only consulted while | |
| 286 | - // this variation is still waiting on the migration; once the sentinel is set, an | |
| 287 | - // empty native gallery is a deliberate choice and is shown as empty. | |
| 288 | - if ( GalleryFns::use_native_gallery() ) { | |
| 289 | - $variation_object = wc_get_product( $variation_id ); | |
| 290 | - | |
| 291 | - if ( $variation_object && $variation_object->is_type( 'variation' ) ) { | |
| 292 | - $native_images = GalleryFns::get_native_variation_gallery_ids( $variation_object ); | |
| 293 | - | |
| 294 | - if ( $native_images || GalleryFns::is_native_gallery_owned( $variation_id ) ) { | |
| 295 | - $gallery_images = $native_images; | |
| 296 | - } | |
| 297 | - } | |
| 298 | - } | |
| 299 | - | |
| 300 | - if ( null === $gallery_images ) { | |
| 301 | - $gallery_images = GalleryFns::get_legacy_variation_gallery_ids( $variation_id ); | |
| 302 | - } | |
| 303 | - | |
| 304 | - /* | |
| 305 | - * WooCommerce renders the variation's main image at thumbnail size (150px) in | |
| 306 | - * `html-variation-admin.php`, which is soft in the wider slot this module uses. | |
| 307 | - * Its own variation gallery hero uses `woocommerce_single`, so the matching URL | |
| 308 | - * is handed to the admin script here — that markup belongs to core and exposes | |
| 309 | - * no filter to size it directly. | |
| 310 | - */ | |
| 311 | - $variation_image_id = absint( get_post_thumbnail_id( $variation_id ) ); | |
| 312 | - $hero_src = $variation_image_id ? wp_get_attachment_image_url( $variation_image_id, 'woocommerce_single' ) : ''; | |
| 136 | + $gallery_images = get_post_meta( $variation_id, 'rtsb_vg_images', true ); | |
| 313 | 137 | ?> |
| 314 | - <div class="form-row form-row-full rtsb-vg-gallery-wrapper" data-hero-src="<?php echo esc_url( $hero_src ); ?>"> | |
| 138 | + <div class="form-row form-row-full rtsb-vg-gallery-wrapper"> | |
| 315 | 139 | <h4><?php esc_html_e( 'Variation Image Gallery', 'shopbuilder' ); ?></h4> |
| 316 | 140 | <div class="rtsb-vg-image-container"> |
| 317 | 141 | <ul class="rtsb-vg-images"> |
| 318 | 142 | <?php |
| @@ -335,11 +159,11 @@ | ||
| 335 | 159 | <li class="image<?php echo esc_html( $add_video_class ); ?>"> |
| 336 | 160 | <input type="hidden" name="rtsb_vg[<?php echo absint( $variation_id ); ?>][]" value="<?php echo absint( $image_id ); ?>"> |
| 337 | 161 | <img src="<?php echo esc_url( $image[0] ); ?>"> |
| 338 | 162 | <div class="rtsb-vg-action-button"> |
| 339 | - <span data-tip="<?php esc_attr_e( 'Add Video', 'shopbuilder' ); ?>" class="rtsb-vg-media-video-popup woocommerce-help-tip dashicons dashicons-video-alt3"></span> | |
| 340 | - <span data-tip="<?php esc_attr_e( 'Edit Image', 'shopbuilder' ); ?>" class="rtsb-vg-gallery-edit woocommerce-help-tip dashicons dashicons-edit"></span> | |
| 341 | - <a href="#" class="delete rtsb-vg-remove-image woocommerce-help-tip" data-tip="<?php esc_attr_e( 'Remove', 'shopbuilder' ); ?>"><span class="dashicons dashicons-no"></span></a> | |
| 163 | + <span data-tip="Add Video" class="rtsb-vg-media-video-popup dashicons dashicons-video-alt3"></span> | |
| 164 | + <span data-tip="Edit Image" class="rtsb-vg-gallery-edit dashicons dashicons-edit"></span> | |
| 165 | + <a href="#" class="delete rtsb-vg-remove-image"><span class="dashicons dashicons-no"></span></a> | |
| 342 | 166 | </div> |
| 343 | 167 | </li> |
| 344 | 168 | <?php |
| 345 | 169 | endforeach; |
| @@ -352,8 +176,12 @@ | ||
| 352 | 176 | data-product_variation_id="<?php echo esc_attr( $variation_id ); ?>" |
| 353 | 177 | class="button rtsb-vg-add-image"> |
| 354 | 178 | <?php |
| 355 | 179 | esc_html_e( 'Add Gallery Images', 'shopbuilder' ); |
| 180 | + if ( rtsb()->has_pro() ) { | |
| 181 | + echo ' & '; | |
| 182 | + esc_html_e( 'Videos', 'shopbuilder' ); | |
| 183 | + } | |
| 356 | 184 | ?> |
| 357 | 185 | </a> |
| 358 | 186 | </p> |
| 359 | 187 | </div> |