post_type ) { return; } wp_enqueue_script( 'wcvg-sync', WCVG_PLUGIN_URL . 'assets/js/wcvg-sync.js', array( 'jquery' ), WCVG_VERSION, true ); wp_localize_script( 'wcvg-sync', 'WCVG_Sync', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wcvg_sync_nonce' ), 'productId' => $post->ID, 'label' => __( 'Sync Variation Images', 'addonn-attribute-image-gallery-woocommerce' ), 'working' => __( 'Syncing...', 'addonn-attribute-image-gallery-woocommerce' ), 'noAttr' => __( 'Set a primary attribute first under WooCommerce > Variation Gallery.', 'addonn-attribute-image-gallery-woocommerce' ), 'hasPrimary' => (bool) WCVG_Settings::get_primary_attribute(), ) ); } public function ajax_sync_variations() { check_ajax_referer( 'wcvg_sync_nonce', 'nonce' ); if ( ! current_user_can( 'edit_products' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', 'addonn-attribute-image-gallery-woocommerce' ) ) ); } $product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; $taxonomy = WCVG_Settings::get_primary_attribute(); if ( ! $product_id || ! $taxonomy ) { wp_send_json_error( array( 'message' => __( 'Missing product or primary attribute.', 'addonn-attribute-image-gallery-woocommerce' ) ) ); } $product = wc_get_product( $product_id ); if ( ! $product || ! $product->is_type( 'variable' ) ) { wp_send_json_error( array( 'message' => __( 'Not a variable product.', 'addonn-attribute-image-gallery-woocommerce' ) ) ); } $updated = 0; $skipped = 0; $map = WCVG_Product_Fields::get_product_map( $product_id ); foreach ( $product->get_children() as $variation_id ) { $variation = wc_get_product( $variation_id ); if ( ! $variation ) { continue; } $variation_attributes = $variation->get_attributes(); $term_slug = isset( $variation_attributes[ $taxonomy ] ) ? $variation_attributes[ $taxonomy ] : ''; if ( ! $term_slug ) { $skipped++; continue; } $term = get_term_by( 'slug', $term_slug, $taxonomy ); if ( ! $term || is_wp_error( $term ) || ! isset( $map[ $term->term_id ] ) ) { $skipped++; continue; } $image_id = isset( $map[ $term->term_id ]['image_id'] ) ? (int) $map[ $term->term_id ]['image_id'] : 0; $gallery_ids = isset( $map[ $term->term_id ]['gallery_ids'] ) ? array_filter( array_map( 'absint', (array) $map[ $term->term_id ]['gallery_ids'] ) ) : array(); if ( $image_id ) { $variation->set_image_id( $image_id ); } // The frontend gallery swap (class-wcvg-frontend.php) rebuilds the // whole slider from this single meta value, replacing the native // gallery entirely rather than appending to it. It previously held // only the secondary gallery images, never the main "Select image" // set above — so the main photo was set as the variation's native // WooCommerce thumbnail (used by cart/listing pages) but silently // dropped from the swapped-in slider itself, which then opened on // the first secondary image instead of the intended main photo. // Storing the main image first, followed by the gallery images // (skipping it again if it was also added to the gallery by // mistake), keeps the two in sync. $full_gallery_ids = array(); if ( $image_id ) { $full_gallery_ids[] = $image_id; } foreach ( $gallery_ids as $gallery_image_id ) { if ( ! in_array( $gallery_image_id, $full_gallery_ids, true ) ) { $full_gallery_ids[] = $gallery_image_id; } } $variation->update_meta_data( '_wcvg_gallery_ids', implode( ',', $full_gallery_ids ) ); $variation->save(); $updated++; } wp_send_json_success( array( 'updated' => $updated, 'skipped' => $skipped, 'message' => sprintf( /* translators: 1: updated count 2: skipped count */ __( '%1$d variation(s) updated, %2$d skipped (no image set for that value on this product).', 'addonn-attribute-image-gallery-woocommerce' ), $updated, $skipped ), ) ); } }