Plugin Directory

Changeset 3754927


Ignore:
Timestamp:
10/19/2026 02:14:14 PM (21 hours ago)
Author:
ournest
Message:

Release 1.1.5: variation images, description HTML, and variant thumbnails

Location:
ods-ournest-dropship
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • ods-ournest-dropship/tags/1.1.5/assets/js/mapping-drawer.js

    r3754791 r3754927  
    627627            html += '<tr class="' + rowClass + '" data-vid="' + v.id + '">'
    628628                + '<td class="py-2 pr-2 col-wc-variant">'
     629                + '<div class="ournest-dropship-variant-mapping-table__wc">'
     630                + (v.image ? '<img src="' + ournestDropship.escapeHtml(v.image) + '" class="ournest-dropship-variant-mapping-table__wc-img" alt="">' : '')
     631                + '<div class="ournest-dropship-variant-mapping-table__wc-text">'
    629632                + '<div class="ournest-dropship-variant-mapping-table__wc-title" title="' + ournestDropship.escapeHtml(variationDisplayName(v)) + '">' + ournestDropship.escapeHtml(variationDisplayName(v)) + '</div>'
    630633                    + (isContextRow ? '<span class="ournest-dropship-mapping-order-item-tag">This order item</span>' : '')
    631                     + '</td>'
     634                    + '</div></div></td>'
    632635                + supplierCell
    633636                + '<td class="py-2 pr-2 col-ae-variant">' + aeCell + '</td>'
  • ods-ournest-dropship/tags/1.1.5/assets/js/product.js

    r3754791 r3754927  
    682682            html += `<tr class="border-b hover:bg-white transition variation-row" data-vid="${v.id}" data-attrs="${attrsEncoded}">
    683683                <td class="py-1 px-1">
    684                     <div class="font-medium">${ournestDropship.escapeHtml(attrStr)}</div>
    685                     <div class="text-gray-400">SKU: ${ournestDropship.escapeHtml(v.sku || '')}</div>
     684                    <div class="flex items-center gap-2">
     685                        <img src="${ournestDropship.escapeHtml(v.image || '')}" class="ournest-dropship-variant-mapping-table__wc-img" alt="">
     686                        <div class="min-w-0">
     687                            <div class="font-medium">${ournestDropship.escapeHtml(attrStr)}</div>
     688                            <div class="text-gray-400">SKU: ${ournestDropship.escapeHtml(v.sku || '')}</div>
     689                        </div>
     690                    </div>
    686691                </td>
    687692                <td class="py-1 px-1 align-top"><input type="number" class="ournest-dropship-input ournest-dropship-input--sm var-price" data-vid="${v.id}" value="${v.price || ''}" step="0.01" min="0"></td>
  • ods-ournest-dropship/tags/1.1.5/assets/style.css

    r3754791 r3754927  
    810810    max-height: min(60vh, 480px);
    811811    overflow: auto;
     812}
     813
     814.ournest-dropship-variant-mapping-table__wc {
     815    display: flex;
     816    align-items: center;
     817    gap: 8px;
     818    min-width: 0;
     819}
     820
     821.ournest-dropship-variant-mapping-table__wc-img {
     822    width: 32px;
     823    height: 32px;
     824    border-radius: 6px;
     825    object-fit: cover;
     826    border: 1px solid #e5e7eb;
     827    background: #f3f4f6;
     828    flex-shrink: 0;
     829}
     830
     831.ournest-dropship-variant-mapping-table__wc-text {
     832    min-width: 0;
     833    flex: 1;
    812834}
    813835
  • ods-ournest-dropship/tags/1.1.5/includes/ajax/ajax-mapping.php

    r3754791 r3754927  
    4444        $html = $format( $price );
    4545        return '' !== $html ? $html : '—';
     46}
     47
     48/**
     49 * Thumbnail for a variation row: own image, then parent, then WooCommerce placeholder.
     50 */
     51function ournest_dropship_variation_thumb_url( WC_Product $item, WC_Product $parent ): string {
     52        $image_id = (int) $item->get_image_id();
     53        $url      = $image_id > 0 ? wp_get_attachment_image_url( $image_id, 'thumbnail' ) : false;
     54        if ( ! $url && $parent->get_id() !== $item->get_id() ) {
     55                $parent_id = (int) $parent->get_image_id();
     56                $url       = $parent_id > 0 ? wp_get_attachment_image_url( $parent_id, 'thumbnail' ) : false;
     57        }
     58        if ( ! $url ) {
     59                $url = wc_placeholder_img_src( 'thumbnail' );
     60        }
     61        return (string) $url;
    4662}
    4763
    … …  
    240256                                        'stock_status' => $var->get_stock_status(),
    241257                                        'status'       => $var->get_status(),
    242                                         'image'        => wp_get_attachment_image_url( $var->get_image_id(), 'thumbnail' ),
     258                                        'image'        => ournest_dropship_variation_thumb_url( $var, $product ),
    243259                                        'attributes'   => $norm_attrs,
    244260                                );
    … …  
    254270                                'stock_status' => $product->get_stock_status(),
    255271                                'status'       => $product->get_status(),
    256                                 'image'        => wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' ),
     272                                'image'        => ournest_dropship_variation_thumb_url( $product, $product ),
    257273                                'attributes'   => array(),
    258274                                'is_simple'    => true,
    … …  
    681697                                'name'        => $product->get_name(),
    682698                                'sku'         => $product->get_sku(),
     699                                'image'       => ournest_dropship_variation_thumb_url( $product, $product ),
    683700                                'attributes'  => array(),
    684701                                'mapping'     => $mapping_info[ $product_id ] ?? array(),
    … …  
    697714                                        'name'        => $var->get_name(),
    698715                                        'sku'         => $var->get_sku(),
     716                                        'image'       => ournest_dropship_variation_thumb_url( $var, $product ),
    699717                                        'attributes'  => $var->get_attributes(),
    700718                                        'mapping'     => $mapping_info[ $var_id ] ?? array(),
  • ods-ournest-dropship/tags/1.1.5/includes/class-product-importer.php

    r3754791 r3754927  
    1515        private API_Client $api;
    1616
     17        /**
     18         * Attachment IDs keyed by normalized image URL (reuse across gallery + variation images).
     19         *
     20         * @var array<string,int>
     21         */
     22        private array $image_url_cache = array();
     23
    1724        public function __construct() {
    1825                $this->api = new API_Client();
    … …  
    2936         */
    3037        public function import( string $ae_product_id, array $sku_ids, array $options = array() ): array {
    31                 $requested_id = $ae_product_id;
     38                $this->image_url_cache = array();
     39                $requested_id          = $ae_product_id;
    3240
    3341                // 1. Fetch product detail, then keep the product id ae-server returns.
    … …  
    115123                $image_list = array_slice( $image_list, 0, $max_images );
    116124
    117                 $image_ids = $this->download_images( $image_list, $ae_product_id, $detail['title'] ?? '' );
     125                $image_ids = $this->download_images( $image_list, $detail['title'] ?? '' );
    118126
    119127                if ( ! empty( $image_ids ) ) {
    … …  
    149157                        $product->set_stock_status( $sku_stock > 0 ? 'instock' : 'outofstock' );
    150158                        $product->set_backorders( $backorders );
     159
     160                        $sku_img = $this->sku_image_url( $sku_data );
     161                        if ( $sku_img !== '' ) {
     162                        $sku_image_id = $this->sideload_image( $sku_img, (string) ( $detail['title'] ?? '' ) );
     163                                if ( $sku_image_id > 0 ) {
     164                                        $product->set_image_id( $sku_image_id );
     165                                }
     166                        }
    151167
    152168                        // Dimensions
    … …  
    233249        /**
    234250         * Download images via WordPress media_sideload_image.
     251         * Identical URLs are downloaded once and reused.
    235252         *
    236253         * @param array  $image_urls
    237          * @param string $ae_product_id
    238254         * @param string $product_title
    239255         * @return array  Attachment IDs
    240256         */
    241         private function download_images( array $image_urls, string $ae_product_id, string $product_title ): array {
     257        private function download_images( array $image_urls, string $product_title ): array {
    242258                $image_ids = array();
     259
     260                foreach ( $image_urls as $url ) {
     261                        $attach_id = $this->sideload_image( (string) $url, $product_title );
     262                        if ( $attach_id > 0 && ! in_array( $attach_id, $image_ids, true ) ) {
     263                                $image_ids[] = $attach_id;
     264                        }
     265                }
     266
     267                return $image_ids;
     268        }
     269
     270        /**
     271         * @param array<string,mixed> $sku_data
     272         */
     273        private function sku_image_url( array $sku_data ): string {
     274                return trim( (string) ( $sku_data['skuImg'] ?? $sku_data['image'] ?? $sku_data['skuImage'] ?? '' ) );
     275        }
     276
     277        /**
     278         * Cache key ignores query string so the same AE file is not downloaded twice.
     279         */
     280        private function image_cache_key( string $url ): string {
     281                $url   = trim( $url );
     282                $parts = wp_parse_url( $url );
     283                if ( ! is_array( $parts ) || empty( $parts['host'] ) ) {
     284                        return $url;
     285                }
     286                $scheme = isset( $parts['scheme'] ) ? strtolower( (string) $parts['scheme'] ) : 'https';
     287                $host   = strtolower( (string) $parts['host'] );
     288                $path   = (string) ( $parts['path'] ?? '' );
     289                return $scheme . '://' . $host . $path;
     290        }
     291
     292        /**
     293         * Sideload one image, reusing a previous attachment for the same URL.
     294         */
     295        private function sideload_image( string $url, string $title ): int {
     296                $url = esc_url_raw( trim( $url ) );
     297                if ( $url === '' ) {
     298                        return 0;
     299                }
     300
     301                $key = $this->image_cache_key( $url );
     302                if ( array_key_exists( $key, $this->image_url_cache ) ) {
     303                        return $this->image_url_cache[ $key ];
     304                }
    243305
    244306                if ( ! function_exists( 'media_sideload_image' ) ) {
    … …  
    248310                }
    249311
    250                 foreach ( $image_urls as $index => $url ) {
    251                         if ( empty( $url ) ) {
    252                                 continue;
    253                         }
    254 
    255                         // Timeout protection: set a shorter timeout for single image download
     312                static $timeout_filter_added = false;
     313                if ( ! $timeout_filter_added ) {
    256314                        add_filter(
    257315                                'http_request_timeout',
    258                                 function () {
     316                                static function () {
    259317                                        return 30;
    260318                                }
    261319                        );
    262 
    263                         $file_name = sanitize_file_name( $ae_product_id . '_' . $index . '.jpg' );
    264                         $title     = ! empty( $product_title ) ? $product_title : 'AE Product Image';
    265 
    266                         $attach_id = media_sideload_image( $url, 0, $title, 'id' );
    267 
    268                         if ( is_wp_error( $attach_id ) ) {
    269                                 continue;
    270                         }
    271 
    272                         // Set alt text
    273                         if ( $attach_id ) {
    274                                 update_post_meta( $attach_id, '_wp_attachment_image_alt', $title );
    275                         }
    276 
    277                         $image_ids[] = $attach_id;
    278                 }
    279 
    280                 return $image_ids;
     320                        $timeout_filter_added = true;
     321                }
     322
     323                $alt       = $title !== '' ? $title : 'AE Product Image';
     324                $attach_id = media_sideload_image( $url, 0, $alt, 'id' );
     325                if ( is_wp_error( $attach_id ) || ! $attach_id ) {
     326                        $this->image_url_cache[ $key ] = 0;
     327                        return 0;
     328                }
     329
     330                $attach_id = (int) $attach_id;
     331                update_post_meta( $attach_id, '_wp_attachment_image_alt', $alt );
     332                $this->image_url_cache[ $key ] = $attach_id;
     333                return $attach_id;
    281334        }
    282335
    … …  
    422475                        $variation->set_backorders( $backorders );
    423476
    424                         // Variation image
    425                         $sku_img = $sku_data['skuImg'] ?? $sku_data['image'] ?? $sku_data['skuImage'] ?? '';
    426                         if ( ! empty( $sku_img ) ) {
    427                                 // We don't sideload here for performance; set the AE URL as placeholder
    428                                 // In production, this would download the image
     477                        $sku_img = $this->sku_image_url( $sku_data );
     478                        if ( $sku_img !== '' ) {
     479                                $sku_image_id = $this->sideload_image(
     480                                        $sku_img,
     481                                        (string) ( $detail['title'] ?? '' )
     482                                );
     483                                if ( $sku_image_id > 0 ) {
     484                                        $variation->set_image_id( $sku_image_id );
     485                                }
    429486                        }
    430487
  • ods-ournest-dropship/tags/1.1.5/readme.txt

    r3754791 r3754927  
    144144
    145145* Fixed product edit drawer clearing images from the Description HTML on save.
     146* Import: assign each WooCommerce variation image from the matching AliExpress SKU image, with URL-based deduplication.
     147* Product edit and mapping drawers show each WooCommerce variant thumbnail beside the variant name.
    146148
    147149= 1.1.4 =
    … …  
    223225
    224226= 1.1.5 =
    225 Fixes Description images being cleared when saving from the product edit drawer. Recommended update for all stores.
     227Fixes Description images being cleared on product edit save, maps AliExpress SKU images to WooCommerce variations on import, and shows variant thumbnails in the edit and mapping drawers. Recommended update for all stores.
    226228
    227229= 1.1.4 =
  • ods-ournest-dropship/trunk/assets/js/mapping-drawer.js

    r3754791 r3754927  
    627627            html += '<tr class="' + rowClass + '" data-vid="' + v.id + '">'
    628628                + '<td class="py-2 pr-2 col-wc-variant">'
     629                + '<div class="ournest-dropship-variant-mapping-table__wc">'
     630                + (v.image ? '<img src="' + ournestDropship.escapeHtml(v.image) + '" class="ournest-dropship-variant-mapping-table__wc-img" alt="">' : '')
     631                + '<div class="ournest-dropship-variant-mapping-table__wc-text">'
    629632                + '<div class="ournest-dropship-variant-mapping-table__wc-title" title="' + ournestDropship.escapeHtml(variationDisplayName(v)) + '">' + ournestDropship.escapeHtml(variationDisplayName(v)) + '</div>'
    630633                    + (isContextRow ? '<span class="ournest-dropship-mapping-order-item-tag">This order item</span>' : '')
    631                     + '</td>'
     634                    + '</div></div></td>'
    632635                + supplierCell
    633636                + '<td class="py-2 pr-2 col-ae-variant">' + aeCell + '</td>'
  • ods-ournest-dropship/trunk/assets/js/product.js

    r3754791 r3754927  
    682682            html += `<tr class="border-b hover:bg-white transition variation-row" data-vid="${v.id}" data-attrs="${attrsEncoded}">
    683683                <td class="py-1 px-1">
    684                     <div class="font-medium">${ournestDropship.escapeHtml(attrStr)}</div>
    685                     <div class="text-gray-400">SKU: ${ournestDropship.escapeHtml(v.sku || '')}</div>
     684                    <div class="flex items-center gap-2">
     685                        <img src="${ournestDropship.escapeHtml(v.image || '')}" class="ournest-dropship-variant-mapping-table__wc-img" alt="">
     686                        <div class="min-w-0">
     687                            <div class="font-medium">${ournestDropship.escapeHtml(attrStr)}</div>
     688                            <div class="text-gray-400">SKU: ${ournestDropship.escapeHtml(v.sku || '')}</div>
     689                        </div>
     690                    </div>
    686691                </td>
    687692                <td class="py-1 px-1 align-top"><input type="number" class="ournest-dropship-input ournest-dropship-input--sm var-price" data-vid="${v.id}" value="${v.price || ''}" step="0.01" min="0"></td>
  • ods-ournest-dropship/trunk/assets/style.css

    r3753959 r3754927  
    810810    max-height: min(60vh, 480px);
    811811    overflow: auto;
     812}
     813
     814.ournest-dropship-variant-mapping-table__wc {
     815    display: flex;
     816    align-items: center;
     817    gap: 8px;
     818    min-width: 0;
     819}
     820
     821.ournest-dropship-variant-mapping-table__wc-img {
     822    width: 32px;
     823    height: 32px;
     824    border-radius: 6px;
     825    object-fit: cover;
     826    border: 1px solid #e5e7eb;
     827    background: #f3f4f6;
     828    flex-shrink: 0;
     829}
     830
     831.ournest-dropship-variant-mapping-table__wc-text {
     832    min-width: 0;
     833    flex: 1;
    812834}
    813835
  • ods-ournest-dropship/trunk/includes/ajax/ajax-mapping.php

    r3754791 r3754927  
    4444        $html = $format( $price );
    4545        return '' !== $html ? $html : '—';
     46}
     47
     48/**
     49 * Thumbnail for a variation row: own image, then parent, then WooCommerce placeholder.
     50 */
     51function ournest_dropship_variation_thumb_url( WC_Product $item, WC_Product $parent ): string {
     52        $image_id = (int) $item->get_image_id();
     53        $url      = $image_id > 0 ? wp_get_attachment_image_url( $image_id, 'thumbnail' ) : false;
     54        if ( ! $url && $parent->get_id() !== $item->get_id() ) {
     55                $parent_id = (int) $parent->get_image_id();
     56                $url       = $parent_id > 0 ? wp_get_attachment_image_url( $parent_id, 'thumbnail' ) : false;
     57        }
     58        if ( ! $url ) {
     59                $url = wc_placeholder_img_src( 'thumbnail' );
     60        }
     61        return (string) $url;
    4662}
    4763
    … …  
    240256                                        'stock_status' => $var->get_stock_status(),
    241257                                        'status'       => $var->get_status(),
    242                                         'image'        => wp_get_attachment_image_url( $var->get_image_id(), 'thumbnail' ),
     258                                        'image'        => ournest_dropship_variation_thumb_url( $var, $product ),
    243259                                        'attributes'   => $norm_attrs,
    244260                                );
    … …  
    254270                                'stock_status' => $product->get_stock_status(),
    255271                                'status'       => $product->get_status(),
    256                                 'image'        => wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' ),
     272                                'image'        => ournest_dropship_variation_thumb_url( $product, $product ),
    257273                                'attributes'   => array(),
    258274                                'is_simple'    => true,
    … …  
    681697                                'name'        => $product->get_name(),
    682698                                'sku'         => $product->get_sku(),
     699                                'image'       => ournest_dropship_variation_thumb_url( $product, $product ),
    683700                                'attributes'  => array(),
    684701                                'mapping'     => $mapping_info[ $product_id ] ?? array(),
    … …  
    697714                                        'name'        => $var->get_name(),
    698715                                        'sku'         => $var->get_sku(),
     716                                        'image'       => ournest_dropship_variation_thumb_url( $var, $product ),
    699717                                        'attributes'  => $var->get_attributes(),
    700718                                        'mapping'     => $mapping_info[ $var_id ] ?? array(),
  • ods-ournest-dropship/trunk/includes/class-product-importer.php

    r3718338 r3754927  
    1515        private API_Client $api;
    1616
     17        /**
     18         * Attachment IDs keyed by normalized image URL (reuse across gallery + variation images).
     19         *
     20         * @var array<string,int>
     21         */
     22        private array $image_url_cache = array();
     23
    1724        public function __construct() {
    1825                $this->api = new API_Client();
    … …  
    2936         */
    3037        public function import( string $ae_product_id, array $sku_ids, array $options = array() ): array {
    31                 $requested_id = $ae_product_id;
     38                $this->image_url_cache = array();
     39                $requested_id          = $ae_product_id;
    3240
    3341                // 1. Fetch product detail, then keep the product id ae-server returns.
    … …  
    115123                $image_list = array_slice( $image_list, 0, $max_images );
    116124
    117                 $image_ids = $this->download_images( $image_list, $ae_product_id, $detail['title'] ?? '' );
     125                $image_ids = $this->download_images( $image_list, $detail['title'] ?? '' );
    118126
    119127                if ( ! empty( $image_ids ) ) {
    … …  
    149157                        $product->set_stock_status( $sku_stock > 0 ? 'instock' : 'outofstock' );
    150158                        $product->set_backorders( $backorders );
     159
     160                        $sku_img = $this->sku_image_url( $sku_data );
     161                        if ( $sku_img !== '' ) {
     162                        $sku_image_id = $this->sideload_image( $sku_img, (string) ( $detail['title'] ?? '' ) );
     163                                if ( $sku_image_id > 0 ) {
     164                                        $product->set_image_id( $sku_image_id );
     165                                }
     166                        }
    151167
    152168                        // Dimensions
    … …  
    233249        /**
    234250         * Download images via WordPress media_sideload_image.
     251         * Identical URLs are downloaded once and reused.
    235252         *
    236253         * @param array  $image_urls
    237          * @param string $ae_product_id
    238254         * @param string $product_title
    239255         * @return array  Attachment IDs
    240256         */
    241         private function download_images( array $image_urls, string $ae_product_id, string $product_title ): array {
     257        private function download_images( array $image_urls, string $product_title ): array {
    242258                $image_ids = array();
     259
     260                foreach ( $image_urls as $url ) {
     261                        $attach_id = $this->sideload_image( (string) $url, $product_title );
     262                        if ( $attach_id > 0 && ! in_array( $attach_id, $image_ids, true ) ) {
     263                                $image_ids[] = $attach_id;
     264                        }
     265                }
     266
     267                return $image_ids;
     268        }
     269
     270        /**
     271         * @param array<string,mixed> $sku_data
     272         */
     273        private function sku_image_url( array $sku_data ): string {
     274                return trim( (string) ( $sku_data['skuImg'] ?? $sku_data['image'] ?? $sku_data['skuImage'] ?? '' ) );
     275        }
     276
     277        /**
     278         * Cache key ignores query string so the same AE file is not downloaded twice.
     279         */
     280        private function image_cache_key( string $url ): string {
     281                $url   = trim( $url );
     282                $parts = wp_parse_url( $url );
     283                if ( ! is_array( $parts ) || empty( $parts['host'] ) ) {
     284                        return $url;
     285                }
     286                $scheme = isset( $parts['scheme'] ) ? strtolower( (string) $parts['scheme'] ) : 'https';
     287                $host   = strtolower( (string) $parts['host'] );
     288                $path   = (string) ( $parts['path'] ?? '' );
     289                return $scheme . '://' . $host . $path;
     290        }
     291
     292        /**
     293         * Sideload one image, reusing a previous attachment for the same URL.
     294         */
     295        private function sideload_image( string $url, string $title ): int {
     296                $url = esc_url_raw( trim( $url ) );
     297                if ( $url === '' ) {
     298                        return 0;
     299                }
     300
     301                $key = $this->image_cache_key( $url );
     302                if ( array_key_exists( $key, $this->image_url_cache ) ) {
     303                        return $this->image_url_cache[ $key ];
     304                }
    243305
    244306                if ( ! function_exists( 'media_sideload_image' ) ) {
    … …  
    248310                }
    249311
    250                 foreach ( $image_urls as $index => $url ) {
    251                         if ( empty( $url ) ) {
    252                                 continue;
    253                         }
    254 
    255                         // Timeout protection: set a shorter timeout for single image download
     312                static $timeout_filter_added = false;
     313                if ( ! $timeout_filter_added ) {
    256314                        add_filter(
    257315                                'http_request_timeout',
    258                                 function () {
     316                                static function () {
    259317                                        return 30;
    260318                                }
    261319                        );
    262 
    263                         $file_name = sanitize_file_name( $ae_product_id . '_' . $index . '.jpg' );
    264                         $title     = ! empty( $product_title ) ? $product_title : 'AE Product Image';
    265 
    266                         $attach_id = media_sideload_image( $url, 0, $title, 'id' );
    267 
    268                         if ( is_wp_error( $attach_id ) ) {
    269                                 continue;
    270                         }
    271 
    272                         // Set alt text
    273                         if ( $attach_id ) {
    274                                 update_post_meta( $attach_id, '_wp_attachment_image_alt', $title );
    275                         }
    276 
    277                         $image_ids[] = $attach_id;
    278                 }
    279 
    280                 return $image_ids;
     320                        $timeout_filter_added = true;
     321                }
     322
     323                $alt       = $title !== '' ? $title : 'AE Product Image';
     324                $attach_id = media_sideload_image( $url, 0, $alt, 'id' );
     325                if ( is_wp_error( $attach_id ) || ! $attach_id ) {
     326                        $this->image_url_cache[ $key ] = 0;
     327                        return 0;
     328                }
     329
     330                $attach_id = (int) $attach_id;
     331                update_post_meta( $attach_id, '_wp_attachment_image_alt', $alt );
     332                $this->image_url_cache[ $key ] = $attach_id;
     333                return $attach_id;
    281334        }
    282335
    … …  
    422475                        $variation->set_backorders( $backorders );
    423476
    424                         // Variation image
    425                         $sku_img = $sku_data['skuImg'] ?? $sku_data['image'] ?? $sku_data['skuImage'] ?? '';
    426                         if ( ! empty( $sku_img ) ) {
    427                                 // We don't sideload here for performance; set the AE URL as placeholder
    428                                 // In production, this would download the image
     477                        $sku_img = $this->sku_image_url( $sku_data );
     478                        if ( $sku_img !== '' ) {
     479                                $sku_image_id = $this->sideload_image(
     480                                        $sku_img,
     481                                        (string) ( $detail['title'] ?? '' )
     482                                );
     483                                if ( $sku_image_id > 0 ) {
     484                                        $variation->set_image_id( $sku_image_id );
     485                                }
    429486                        }
    430487
  • ods-ournest-dropship/trunk/readme.txt

    r3754791 r3754927  
    145145* Fixed product edit drawer clearing images from the Description HTML on save.
    146146* Mapping drawer: vertically center the “Don’t auto-sync stock” checkbox with its label.
     147* Import: assign each WooCommerce variation image from the matching AliExpress SKU image, with URL-based deduplication.
     148* Product edit and mapping drawers show each WooCommerce variant thumbnail beside the variant name.
    147149
    148150= 1.1.4 =
    … …  
    224226
    225227= 1.1.5 =
    226 Fixes Description images being cleared when saving from the product edit drawer. Recommended update for all stores.
     228Fixes Description images being cleared on product edit save, maps AliExpress SKU images to WooCommerce variations on import, and shows variant thumbnails in the edit and mapping drawers. Recommended update for all stores.
    227229
    228230= 1.1.4 =
Note: See TracChangeset for help on using the changeset viewer.