Changeset 3754927
- Timestamp:
- 10/19/2026 02:14:14 PM (21 hours ago)
- Location:
- ods-ournest-dropship
- Files:
-
- 12 edited
-
tags/1.1.5/assets/js/mapping-drawer.js (modified) (1 diff)
-
tags/1.1.5/assets/js/product.js (modified) (1 diff)
-
tags/1.1.5/assets/style.css (modified) (1 diff)
-
tags/1.1.5/includes/ajax/ajax-mapping.php (modified) (5 diffs)
-
tags/1.1.5/includes/class-product-importer.php (modified) (7 diffs)
-
tags/1.1.5/readme.txt (modified) (2 diffs)
-
trunk/assets/js/mapping-drawer.js (modified) (1 diff)
-
trunk/assets/js/product.js (modified) (1 diff)
-
trunk/assets/style.css (modified) (1 diff)
-
trunk/includes/ajax/ajax-mapping.php (modified) (5 diffs)
-
trunk/includes/class-product-importer.php (modified) (7 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ods-ournest-dropship/tags/1.1.5/assets/js/mapping-drawer.js
r3754791 r3754927 627 627 html += '<tr class="' + rowClass + '" data-vid="' + v.id + '">' 628 628 + '<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">' 629 632 + '<div class="ournest-dropship-variant-mapping-table__wc-title" title="' + ournestDropship.escapeHtml(variationDisplayName(v)) + '">' + ournestDropship.escapeHtml(variationDisplayName(v)) + '</div>' 630 633 + (isContextRow ? '<span class="ournest-dropship-mapping-order-item-tag">This order item</span>' : '') 631 + '</ td>'634 + '</div></div></td>' 632 635 + supplierCell 633 636 + '<td class="py-2 pr-2 col-ae-variant">' + aeCell + '</td>' -
ods-ournest-dropship/tags/1.1.5/assets/js/product.js
r3754791 r3754927 682 682 html += `<tr class="border-b hover:bg-white transition variation-row" data-vid="${v.id}" data-attrs="${attrsEncoded}"> 683 683 <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> 686 691 </td> 687 692 <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 810 810 max-height: min(60vh, 480px); 811 811 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; 812 834 } 813 835 -
ods-ournest-dropship/tags/1.1.5/includes/ajax/ajax-mapping.php
r3754791 r3754927 44 44 $html = $format( $price ); 45 45 return '' !== $html ? $html : '—'; 46 } 47 48 /** 49 * Thumbnail for a variation row: own image, then parent, then WooCommerce placeholder. 50 */ 51 function 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; 46 62 } 47 63 … … 240 256 'stock_status' => $var->get_stock_status(), 241 257 '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 ), 243 259 'attributes' => $norm_attrs, 244 260 ); … … 254 270 'stock_status' => $product->get_stock_status(), 255 271 '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 ), 257 273 'attributes' => array(), 258 274 'is_simple' => true, … … 681 697 'name' => $product->get_name(), 682 698 'sku' => $product->get_sku(), 699 'image' => ournest_dropship_variation_thumb_url( $product, $product ), 683 700 'attributes' => array(), 684 701 'mapping' => $mapping_info[ $product_id ] ?? array(), … … 697 714 'name' => $var->get_name(), 698 715 'sku' => $var->get_sku(), 716 'image' => ournest_dropship_variation_thumb_url( $var, $product ), 699 717 'attributes' => $var->get_attributes(), 700 718 'mapping' => $mapping_info[ $var_id ] ?? array(), -
ods-ournest-dropship/tags/1.1.5/includes/class-product-importer.php
r3754791 r3754927 15 15 private API_Client $api; 16 16 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 17 24 public function __construct() { 18 25 $this->api = new API_Client(); … … 29 36 */ 30 37 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; 32 40 33 41 // 1. Fetch product detail, then keep the product id ae-server returns. … … 115 123 $image_list = array_slice( $image_list, 0, $max_images ); 116 124 117 $image_ids = $this->download_images( $image_list, $ ae_product_id, $detail['title'] ?? '' );125 $image_ids = $this->download_images( $image_list, $detail['title'] ?? '' ); 118 126 119 127 if ( ! empty( $image_ids ) ) { … … 149 157 $product->set_stock_status( $sku_stock > 0 ? 'instock' : 'outofstock' ); 150 158 $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 } 151 167 152 168 // Dimensions … … 233 249 /** 234 250 * Download images via WordPress media_sideload_image. 251 * Identical URLs are downloaded once and reused. 235 252 * 236 253 * @param array $image_urls 237 * @param string $ae_product_id238 254 * @param string $product_title 239 255 * @return array Attachment IDs 240 256 */ 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 { 242 258 $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 } 243 305 244 306 if ( ! function_exists( 'media_sideload_image' ) ) { … … 248 310 } 249 311 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 ) { 256 314 add_filter( 257 315 'http_request_timeout', 258 function () {316 static function () { 259 317 return 30; 260 318 } 261 319 ); 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; 281 334 } 282 335 … … 422 475 $variation->set_backorders( $backorders ); 423 476 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 } 429 486 } 430 487 -
ods-ournest-dropship/tags/1.1.5/readme.txt
r3754791 r3754927 144 144 145 145 * 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. 146 148 147 149 = 1.1.4 = … … 223 225 224 226 = 1.1.5 = 225 Fixes Description images being cleared when saving from the product edit drawer. Recommended update for all stores.227 Fixes 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. 226 228 227 229 = 1.1.4 = -
ods-ournest-dropship/trunk/assets/js/mapping-drawer.js
r3754791 r3754927 627 627 html += '<tr class="' + rowClass + '" data-vid="' + v.id + '">' 628 628 + '<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">' 629 632 + '<div class="ournest-dropship-variant-mapping-table__wc-title" title="' + ournestDropship.escapeHtml(variationDisplayName(v)) + '">' + ournestDropship.escapeHtml(variationDisplayName(v)) + '</div>' 630 633 + (isContextRow ? '<span class="ournest-dropship-mapping-order-item-tag">This order item</span>' : '') 631 + '</ td>'634 + '</div></div></td>' 632 635 + supplierCell 633 636 + '<td class="py-2 pr-2 col-ae-variant">' + aeCell + '</td>' -
ods-ournest-dropship/trunk/assets/js/product.js
r3754791 r3754927 682 682 html += `<tr class="border-b hover:bg-white transition variation-row" data-vid="${v.id}" data-attrs="${attrsEncoded}"> 683 683 <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> 686 691 </td> 687 692 <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 810 810 max-height: min(60vh, 480px); 811 811 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; 812 834 } 813 835 -
ods-ournest-dropship/trunk/includes/ajax/ajax-mapping.php
r3754791 r3754927 44 44 $html = $format( $price ); 45 45 return '' !== $html ? $html : '—'; 46 } 47 48 /** 49 * Thumbnail for a variation row: own image, then parent, then WooCommerce placeholder. 50 */ 51 function 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; 46 62 } 47 63 … … 240 256 'stock_status' => $var->get_stock_status(), 241 257 '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 ), 243 259 'attributes' => $norm_attrs, 244 260 ); … … 254 270 'stock_status' => $product->get_stock_status(), 255 271 '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 ), 257 273 'attributes' => array(), 258 274 'is_simple' => true, … … 681 697 'name' => $product->get_name(), 682 698 'sku' => $product->get_sku(), 699 'image' => ournest_dropship_variation_thumb_url( $product, $product ), 683 700 'attributes' => array(), 684 701 'mapping' => $mapping_info[ $product_id ] ?? array(), … … 697 714 'name' => $var->get_name(), 698 715 'sku' => $var->get_sku(), 716 'image' => ournest_dropship_variation_thumb_url( $var, $product ), 699 717 'attributes' => $var->get_attributes(), 700 718 'mapping' => $mapping_info[ $var_id ] ?? array(), -
ods-ournest-dropship/trunk/includes/class-product-importer.php
r3718338 r3754927 15 15 private API_Client $api; 16 16 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 17 24 public function __construct() { 18 25 $this->api = new API_Client(); … … 29 36 */ 30 37 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; 32 40 33 41 // 1. Fetch product detail, then keep the product id ae-server returns. … … 115 123 $image_list = array_slice( $image_list, 0, $max_images ); 116 124 117 $image_ids = $this->download_images( $image_list, $ ae_product_id, $detail['title'] ?? '' );125 $image_ids = $this->download_images( $image_list, $detail['title'] ?? '' ); 118 126 119 127 if ( ! empty( $image_ids ) ) { … … 149 157 $product->set_stock_status( $sku_stock > 0 ? 'instock' : 'outofstock' ); 150 158 $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 } 151 167 152 168 // Dimensions … … 233 249 /** 234 250 * Download images via WordPress media_sideload_image. 251 * Identical URLs are downloaded once and reused. 235 252 * 236 253 * @param array $image_urls 237 * @param string $ae_product_id238 254 * @param string $product_title 239 255 * @return array Attachment IDs 240 256 */ 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 { 242 258 $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 } 243 305 244 306 if ( ! function_exists( 'media_sideload_image' ) ) { … … 248 310 } 249 311 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 ) { 256 314 add_filter( 257 315 'http_request_timeout', 258 function () {316 static function () { 259 317 return 30; 260 318 } 261 319 ); 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; 281 334 } 282 335 … … 422 475 $variation->set_backorders( $backorders ); 423 476 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 } 429 486 } 430 487 -
ods-ournest-dropship/trunk/readme.txt
r3754791 r3754927 145 145 * Fixed product edit drawer clearing images from the Description HTML on save. 146 146 * 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. 147 149 148 150 = 1.1.4 = … … 224 226 225 227 = 1.1.5 = 226 Fixes Description images being cleared when saving from the product edit drawer. Recommended update for all stores.228 Fixes 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. 227 229 228 230 = 1.1.4 =
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)