Changeset 3408329
- Timestamp:
- 12/02/2025 05:50:28 PM (4 months ago)
- Location:
- transfer-brands-for-woocommerce
- Files:
-
- 12 edited
- 1 copied
-
tags/2.8.5 (copied) (copied from transfer-brands-for-woocommerce/trunk)
-
tags/2.8.5/includes/class-admin.php (modified) (1 diff)
-
tags/2.8.5/includes/class-ajax.php (modified) (5 diffs)
-
tags/2.8.5/includes/class-transfer.php (modified) (4 diffs)
-
tags/2.8.5/includes/class-utils.php (modified) (1 diff)
-
tags/2.8.5/readme.txt (modified) (3 diffs)
-
tags/2.8.5/transfer-brands-for-woocommerce.php (modified) (2 diffs)
-
trunk/includes/class-admin.php (modified) (1 diff)
-
trunk/includes/class-ajax.php (modified) (5 diffs)
-
trunk/includes/class-transfer.php (modified) (4 diffs)
-
trunk/includes/class-utils.php (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/transfer-brands-for-woocommerce.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
transfer-brands-for-woocommerce/tags/2.8.5/includes/class-admin.php
r3408293 r3408329 303 303 * 304 304 * @since 2.3.0 305 * @since 2.8.5 Added support for Perfect Brands for WooCommerce (pwb-brand) 305 306 */ 306 307 public function source_taxonomy_callback() { 307 308 $attribute_taxonomies = wc_get_attribute_taxonomies(); 308 309 $source = $this->core->get_option('source_taxonomy', 'pa_brand'); 309 310 310 311 echo '<select name="tbfw_transfer_brands_options[source_taxonomy]">'; 311 312 foreach ($attribute_taxonomies as $tax) { 313 $tax_name = 'pa_' . $tax->attribute_name; 314 echo '<option value="' . esc_attr($tax_name) . '" ' . selected($tax_name, $source, false) . '>'; 315 echo esc_html($tax->attribute_label) . ' (' . esc_html($tax_name) . ')'; 316 echo '</option>'; 312 313 // WooCommerce Product Attributes section 314 if (!empty($attribute_taxonomies)) { 315 echo '<optgroup label="' . esc_attr__('WooCommerce Attributes', 'transfer-brands-for-woocommerce') . '">'; 316 foreach ($attribute_taxonomies as $tax) { 317 $tax_name = 'pa_' . $tax->attribute_name; 318 echo '<option value="' . esc_attr($tax_name) . '" ' . selected($tax_name, $source, false) . '>'; 319 echo esc_html($tax->attribute_label) . ' (' . esc_html($tax_name) . ')'; 320 echo '</option>'; 321 } 322 echo '</optgroup>'; 317 323 } 318 324 325 // Brand Plugins section - check for supported brand plugin taxonomies 326 $brand_plugins = $this->get_supported_brand_plugins(); 327 if (!empty($brand_plugins)) { 328 echo '<optgroup label="' . esc_attr__('Brand Plugins', 'transfer-brands-for-woocommerce') . '">'; 329 foreach ($brand_plugins as $plugin) { 330 echo '<option value="' . esc_attr($plugin['taxonomy']) . '" ' . selected($plugin['taxonomy'], $source, false) . '>'; 331 echo esc_html($plugin['label']); 332 echo '</option>'; 333 } 334 echo '</optgroup>'; 335 } 336 319 337 echo '</select>'; 320 echo '<p class="description">' . esc_html__('Select the source attribute that contains your brands.', 'transfer-brands-for-woocommerce') . '</p>'; 338 echo '<p class="description">' . esc_html__('Select the source attribute or taxonomy that contains your brands.', 'transfer-brands-for-woocommerce') . '</p>'; 339 340 // Show info about detected brand plugins 341 if (!empty($brand_plugins)) { 342 echo '<p class="description" style="color: #2271b1;"><span class="dashicons dashicons-info" style="font-size: 14px; width: 14px; height: 14px;"></span> '; 343 echo esc_html__('Detected brand plugin(s):', 'transfer-brands-for-woocommerce') . ' '; 344 $plugin_names = array_column($brand_plugins, 'name'); 345 echo '<strong>' . esc_html(implode(', ', $plugin_names)) . '</strong>'; 346 echo '</p>'; 347 } 348 } 349 350 /** 351 * Get list of supported brand plugins that are active 352 * 353 * @since 2.8.5 354 * @return array Array of supported brand plugins with their taxonomies 355 */ 356 private function get_supported_brand_plugins() { 357 $plugins = []; 358 359 // Perfect Brands for WooCommerce 360 if (taxonomy_exists('pwb-brand')) { 361 $plugins[] = [ 362 'taxonomy' => 'pwb-brand', 363 'name' => 'Perfect Brands for WooCommerce', 364 'label' => __('Perfect Brands (pwb-brand)', 'transfer-brands-for-woocommerce'), 365 'image_meta_key' => 'pwb_brand_image' 366 ]; 367 } 368 369 // YITH WooCommerce Brands (uses yith_product_brand taxonomy) 370 if (taxonomy_exists('yith_product_brand')) { 371 $plugins[] = [ 372 'taxonomy' => 'yith_product_brand', 373 'name' => 'YITH WooCommerce Brands', 374 'label' => __('YITH Brands (yith_product_brand)', 'transfer-brands-for-woocommerce'), 375 'image_meta_key' => 'yith_woocommerce_brand_thumbnail_id' 376 ]; 377 } 378 379 return $plugins; 321 380 } 322 381 -
transfer-brands-for-woocommerce/tags/2.8.5/includes/class-ajax.php
r3408293 r3408329 134 134 /** 135 135 * AJAX handler for checking brands 136 * 137 * @since 2.8.5 Added support for brand plugin taxonomies (pwb-brand, etc.) 136 138 */ 137 139 public function ajax_check_brands() { 138 140 check_ajax_referer('tbfw_transfer_brands_nonce', 'nonce'); 139 140 if (!current_user_can('manage_woocommerce')) { 141 wp_die(__('You do not have permission to perform this action.', 'transfer-brands-for-woocommerce')); 142 } 143 141 142 if (!current_user_can('manage_woocommerce')) { 143 wp_die(__('You do not have permission to perform this action.', 'transfer-brands-for-woocommerce')); 144 } 145 146 $source_taxonomy = $this->core->get_option('source_taxonomy'); 147 $is_brand_plugin = $this->core->get_utils()->is_brand_plugin_taxonomy($source_taxonomy); 148 144 149 $source_terms = get_terms([ 145 'taxonomy' => $ this->core->get_option('source_taxonomy'),150 'taxonomy' => $source_taxonomy, 146 151 'hide_empty' => false 147 152 ]); 148 153 149 154 if (is_wp_error($source_terms)) { 150 155 wp_send_json_error(['message' => 'Error: ' . $source_terms->get_error_message()]); 151 156 return; 152 157 } 153 158 154 159 global $wpdb; 155 156 // Get info about custom attributes 157 $custom_attribute_count = $wpdb->get_var( 158 $wpdb->prepare( 159 "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} 160 WHERE meta_key = '_product_attributes' 161 AND meta_value LIKE %s 162 AND meta_value LIKE %s 163 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 164 '%' . $wpdb->esc_like($this->core->get_option('source_taxonomy')) . '%', 165 '%"is_taxonomy";i:0;%' 166 ) 167 ); 168 169 // Sample of products with custom attributes 170 $custom_products = $wpdb->get_results( 171 $wpdb->prepare( 172 "SELECT post_id, meta_value FROM {$wpdb->postmeta} 173 WHERE meta_key = '_product_attributes' 174 AND meta_value LIKE %s 175 AND meta_value LIKE %s 176 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish') 177 LIMIT 10", 178 '%' . $wpdb->esc_like($this->core->get_option('source_taxonomy')) . '%', 179 '%"is_taxonomy";i:0;%' 180 ) 181 ); 182 160 161 $custom_attribute_count = 0; 183 162 $custom_samples = []; 184 foreach ($custom_products as $item) { 185 $attributes = maybe_unserialize($item->meta_value); 186 if (is_array($attributes) && isset($attributes[$this->core->get_option('source_taxonomy')])) { 187 $product = wc_get_product($item->post_id); 188 if ($product) { 189 $custom_samples[] = [ 190 'id' => $item->post_id, 191 'name' => $product->get_name(), 192 'value' => $attributes[$this->core->get_option('source_taxonomy')]['value'] ?? 'N/A' 193 ]; 163 $sample_products = []; 164 165 // For brand plugin taxonomies, skip custom attribute checks (they don't use _product_attributes) 166 if (!$is_brand_plugin) { 167 // Get info about custom attributes (only for WooCommerce attributes) 168 $custom_attribute_count = $wpdb->get_var( 169 $wpdb->prepare( 170 "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} 171 WHERE meta_key = '_product_attributes' 172 AND meta_value LIKE %s 173 AND meta_value LIKE %s 174 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 175 '%' . $wpdb->esc_like($source_taxonomy) . '%', 176 '%"is_taxonomy";i:0;%' 177 ) 178 ); 179 180 // Sample of products with custom attributes 181 $custom_products = $wpdb->get_results( 182 $wpdb->prepare( 183 "SELECT post_id, meta_value FROM {$wpdb->postmeta} 184 WHERE meta_key = '_product_attributes' 185 AND meta_value LIKE %s 186 AND meta_value LIKE %s 187 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish') 188 LIMIT 10", 189 '%' . $wpdb->esc_like($source_taxonomy) . '%', 190 '%"is_taxonomy";i:0;%' 191 ) 192 ); 193 194 foreach ($custom_products as $item) { 195 $attributes = maybe_unserialize($item->meta_value); 196 if (is_array($attributes) && isset($attributes[$source_taxonomy])) { 197 $product = wc_get_product($item->post_id); 198 if ($product) { 199 $custom_samples[] = [ 200 'id' => $item->post_id, 201 'name' => $product->get_name(), 202 'value' => $attributes[$source_taxonomy]['value'] ?? 'N/A' 203 ]; 204 } 194 205 } 195 206 } 196 207 } 197 208 198 209 // Check if any terms already exist in destination 199 210 $conflicting_terms = []; 200 211 $terms_with_images = 0; 201 212 202 213 foreach ($source_terms as $term) { 203 214 $exists = term_exists($term->name, $this->core->get_option('destination_taxonomy')); … … 205 216 $conflicting_terms[] = $term->name; 206 217 } 207 218 208 219 // Check if term has image using the comprehensive detection method 209 220 $transfer_instance = $this->core->get_transfer(); … … 213 224 $method->setAccessible(true); 214 225 $image_id = $method->invoke($transfer_instance, $term->term_id); 215 226 216 227 if ($image_id) { 217 228 $terms_with_images++; 218 229 } 219 230 } 220 221 // Get some sample products with taxonomy attributes 222 $products_query = new WP_Query([ 223 'post_type' => 'product', 224 'posts_per_page' => 5, 225 'tax_query' => [ 226 [ 227 'taxonomy' => $this->core->get_option('source_taxonomy'), 228 'operator' => 'EXISTS', 231 232 // Get sample products - different logic for brand plugins vs WooCommerce attributes 233 if ($is_brand_plugin) { 234 // For brand plugins, query products via taxonomy relationship 235 $products_query = new WP_Query([ 236 'post_type' => 'product', 237 'posts_per_page' => 5, 238 'post_status' => 'publish', 239 'tax_query' => [ 240 [ 241 'taxonomy' => $source_taxonomy, 242 'operator' => 'EXISTS', 243 ] 229 244 ] 230 ] 231 ]); 232 233 $sample_products = []; 234 235 if ($products_query->have_posts()) { 236 foreach ($products_query->posts as $post) { 237 $product = wc_get_product($post->ID); 238 $attrs = $product->get_attributes(); 239 240 if (isset($attrs[$this->core->get_option('source_taxonomy')])) { 241 $terms = []; 242 foreach ($attrs[$this->core->get_option('source_taxonomy')]->get_options() as $term_id) { 243 $term = get_term($term_id, $this->core->get_option('source_taxonomy')); 244 if ($term && !is_wp_error($term)) { 245 $terms[] = $term->name; 246 } 245 ]); 246 247 if ($products_query->have_posts()) { 248 foreach ($products_query->posts as $post) { 249 $product = wc_get_product($post->ID); 250 if (!$product) continue; 251 252 // Get terms directly from taxonomy 253 $product_terms = get_the_terms($post->ID, $source_taxonomy); 254 $term_names = []; 255 256 if ($product_terms && !is_wp_error($product_terms)) { 257 $term_names = wp_list_pluck($product_terms, 'name'); 247 258 } 248 259 249 260 $sample_products[] = [ 250 261 'id' => $post->ID, 251 262 'name' => $product->get_name(), 252 'brands' => $term s263 'brands' => $term_names 253 264 ]; 254 265 } 255 266 } 256 } 257 267 } else { 268 // For WooCommerce attributes, use the original query 269 $products_query = new WP_Query([ 270 'post_type' => 'product', 271 'posts_per_page' => 5, 272 'tax_query' => [ 273 [ 274 'taxonomy' => $source_taxonomy, 275 'operator' => 'EXISTS', 276 ] 277 ] 278 ]); 279 280 if ($products_query->have_posts()) { 281 foreach ($products_query->posts as $post) { 282 $product = wc_get_product($post->ID); 283 $attrs = $product->get_attributes(); 284 285 if (isset($attrs[$source_taxonomy])) { 286 $terms = []; 287 foreach ($attrs[$source_taxonomy]->get_options() as $term_id) { 288 $term = get_term($term_id, $source_taxonomy); 289 if ($term && !is_wp_error($term)) { 290 $terms[] = $term->name; 291 } 292 } 293 294 $sample_products[] = [ 295 'id' => $post->ID, 296 'name' => $product->get_name(), 297 'brands' => $terms 298 ]; 299 } 300 } 301 } 302 } 303 258 304 // Add debug info 259 305 $this->core->add_debug("Brand analysis performed", [ 260 306 'source_terms' => count($source_terms), 307 'is_brand_plugin' => $is_brand_plugin, 261 308 'custom_attribute_count' => $custom_attribute_count, 262 309 'taxonomy_samples' => count($sample_products), … … 297 344 298 345 $html .= '<h4>' . esc_html__('Source Brands Summary', 'transfer-brands-for-woocommerce') . '</h4>'; 299 $html .= '<ul>'; 300 $html .= '<li><strong>' . count($source_terms) . '</strong> ' . esc_html__('brands found in taxonomy', 'transfer-brands-for-woocommerce') . ' ' . esc_html($this->core->get_option('source_taxonomy')) . '</li>'; 301 $html .= '<li><strong>' . $custom_attribute_count . '</strong> ' . esc_html__('products have custom (non-taxonomy) attributes with name', 'transfer-brands-for-woocommerce') . ' ' . esc_html($this->core->get_option('source_taxonomy')) . '</li>'; 302 $html .= '<li><strong>' . $terms_with_images . '</strong> ' . esc_html__('brands have images that will be transferred', 'transfer-brands-for-woocommerce') . '</li>'; 303 $html .= '</ul>'; 304 305 // Warning about custom attributes 306 if ($custom_attribute_count > 0) { 346 347 // Show different info for brand plugins vs WooCommerce attributes 348 if ($is_brand_plugin) { 349 $html .= '<div class="notice notice-info inline" style="margin: 0 0 15px 0; padding: 10px 12px;">'; 350 $html .= '<p style="margin: 0;"><span class="dashicons dashicons-info" style="color: #2271b1;"></span> '; 351 $html .= '<strong>' . esc_html__('Brand Plugin Detected:', 'transfer-brands-for-woocommerce') . '</strong> '; 352 $html .= esc_html__('Transferring from a third-party brand plugin taxonomy.', 'transfer-brands-for-woocommerce'); 353 $html .= '</p></div>'; 354 355 // Get product count for brand plugin 356 $brand_plugin_product_count = $this->core->get_utils()->count_products_with_source(); 357 358 $html .= '<ul>'; 359 $html .= '<li><strong>' . count($source_terms) . '</strong> ' . esc_html__('brands found in taxonomy', 'transfer-brands-for-woocommerce') . ' <code>' . esc_html($source_taxonomy) . '</code></li>'; 360 $html .= '<li><strong>' . $brand_plugin_product_count . '</strong> ' . esc_html__('products have brands assigned', 'transfer-brands-for-woocommerce') . '</li>'; 361 $html .= '<li><strong>' . $terms_with_images . '</strong> ' . esc_html__('brands have images that will be transferred', 'transfer-brands-for-woocommerce') . '</li>'; 362 $html .= '</ul>'; 363 } else { 364 $html .= '<ul>'; 365 $html .= '<li><strong>' . count($source_terms) . '</strong> ' . esc_html__('brands found in taxonomy', 'transfer-brands-for-woocommerce') . ' <code>' . esc_html($source_taxonomy) . '</code></li>'; 366 $html .= '<li><strong>' . $custom_attribute_count . '</strong> ' . esc_html__('products have custom (non-taxonomy) attributes with name', 'transfer-brands-for-woocommerce') . ' <code>' . esc_html($source_taxonomy) . '</code></li>'; 367 $html .= '<li><strong>' . $terms_with_images . '</strong> ' . esc_html__('brands have images that will be transferred', 'transfer-brands-for-woocommerce') . '</li>'; 368 $html .= '</ul>'; 369 } 370 371 // Warning about custom attributes (only for WooCommerce attributes) 372 if (!$is_brand_plugin && $custom_attribute_count > 0) { 307 373 $html .= '<div class="notice notice-warning inline" style="margin-top: 15px;">'; 308 374 $html .= '<p><strong>Custom Attributes Detected:</strong> Some of your products use custom (non-taxonomy) attributes for brands.</p>'; … … 349 415 350 416 if (!empty($sample_products)) { 351 $html .= '<h4>Sample Taxonomy Products</h4>'; 352 $html .= '<p>Here are some products with taxonomy brand attributes:</p>'; 417 if ($is_brand_plugin) { 418 $html .= '<h4>' . esc_html__('Sample Products with Brand Plugin Brands', 'transfer-brands-for-woocommerce') . '</h4>'; 419 $html .= '<p>' . esc_html__('Here are some products with brands from the brand plugin:', 'transfer-brands-for-woocommerce') . '</p>'; 420 } else { 421 $html .= '<h4>' . esc_html__('Sample Products with Brand Attributes', 'transfer-brands-for-woocommerce') . '</h4>'; 422 $html .= '<p>' . esc_html__('Here are some products with taxonomy brand attributes:', 'transfer-brands-for-woocommerce') . '</p>'; 423 } 353 424 $html .= '<table class="widefat" style="margin-top: 10px;">'; 354 $html .= '<thead><tr><th>ID</th><th> Product</th><th>Current Brands</th></tr></thead>';425 $html .= '<thead><tr><th>ID</th><th>' . esc_html__('Product', 'transfer-brands-for-woocommerce') . '</th><th>' . esc_html__('Current Brands', 'transfer-brands-for-woocommerce') . '</th></tr></thead>'; 355 426 $html .= '<tbody>'; 356 427 357 428 foreach ($sample_products as $product) { 358 429 $html .= '<tr>'; 359 $html .= '<td>' . $product['id']. '</td>';430 $html .= '<td>' . esc_html($product['id']) . '</td>'; 360 431 $html .= '<td>' . esc_html($product['name']) . '</td>'; 361 432 $html .= '<td>' . esc_html(implode(', ', $product['brands'])) . '</td>'; 362 433 $html .= '</tr>'; 363 434 } 364 435 365 436 $html .= '</tbody></table>'; 366 437 } -
transfer-brands-for-woocommerce/tags/2.8.5/includes/class-transfer.php
r3408293 r3408329 120 120 /** 121 121 * Process a batch of products 122 * 122 * 123 * @since 2.8.5 Added support for brand plugin taxonomies (pwb-brand, etc.) 123 124 * @return array Result data 124 125 */ 125 126 public function process_products_batch() { 126 127 global $wpdb; 127 128 129 $source_taxonomy = $this->core->get_option('source_taxonomy'); 130 $is_brand_plugin = $this->core->get_utils()->is_brand_plugin_taxonomy($source_taxonomy); 131 128 132 // Get products that have already been processed 129 133 $processed_products = get_option('tbfw_brands_processed_ids', []); 130 131 // Create exclusion placeholders for already processed products 132 $exclude_condition = ''; 133 $query_args = [ 134 '%' . $wpdb->esc_like($this->core->get_option('source_taxonomy')) . '%' 135 ]; 136 137 if (!empty($processed_products)) { 138 $placeholders = implode(',', array_fill(0, count($processed_products), '%d')); 139 $exclude_condition = " AND post_id NOT IN ($placeholders)"; 140 $query_args = array_merge($query_args, $processed_products); 141 } 142 143 $query_args[] = $this->core->get_batch_size(); 144 145 // Find products with brand attribute that haven't been processed yet 146 $query = "SELECT DISTINCT post_id 147 FROM {$wpdb->postmeta} 148 WHERE meta_key = '_product_attributes' 149 AND meta_value LIKE %s 150 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish') 151 {$exclude_condition} 152 LIMIT %d"; 153 154 $product_ids = $wpdb->get_col( 155 $wpdb->prepare($query, $query_args) 156 ); 157 158 // Count total products for progress calculation 159 $total = $wpdb->get_var( 160 $wpdb->prepare( 161 "SELECT COUNT(DISTINCT post_id) 162 FROM {$wpdb->postmeta} 163 WHERE meta_key = '_product_attributes' 164 AND meta_value LIKE %s 165 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 166 '%' . $wpdb->esc_like($this->core->get_option('source_taxonomy')) . '%' 167 ) 168 ); 169 $this->core->add_debug("Total products with source brand", ['total' => $total]); 134 135 // Different query logic for brand plugin taxonomies vs WooCommerce attributes 136 if ($is_brand_plugin) { 137 // For brand plugin taxonomies, query products via taxonomy relationship 138 $product_ids = $this->get_brand_plugin_products($source_taxonomy, $processed_products); 139 $total = $this->count_brand_plugin_products($source_taxonomy); 140 } else { 141 // For WooCommerce attributes, use the original _product_attributes meta query 142 $exclude_condition = ''; 143 $query_args = [ 144 '%' . $wpdb->esc_like($source_taxonomy) . '%' 145 ]; 146 147 if (!empty($processed_products)) { 148 $placeholders = implode(',', array_fill(0, count($processed_products), '%d')); 149 $exclude_condition = " AND post_id NOT IN ($placeholders)"; 150 $query_args = array_merge($query_args, $processed_products); 151 } 152 153 $query_args[] = $this->core->get_batch_size(); 154 155 // Find products with brand attribute that haven't been processed yet 156 $query = "SELECT DISTINCT post_id 157 FROM {$wpdb->postmeta} 158 WHERE meta_key = '_product_attributes' 159 AND meta_value LIKE %s 160 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish') 161 {$exclude_condition} 162 LIMIT %d"; 163 164 $product_ids = $wpdb->get_col( 165 $wpdb->prepare($query, $query_args) 166 ); 167 168 // Count total products for progress calculation 169 $total = $wpdb->get_var( 170 $wpdb->prepare( 171 "SELECT COUNT(DISTINCT post_id) 172 FROM {$wpdb->postmeta} 173 WHERE meta_key = '_product_attributes' 174 AND meta_value LIKE %s 175 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 176 '%' . $wpdb->esc_like($source_taxonomy) . '%' 177 ) 178 ); 179 } 180 181 $this->core->add_debug("Total products with source brand", [ 182 'total' => $total, 183 'is_brand_plugin' => $is_brand_plugin 184 ]); 170 185 171 186 // If there are no more products to process, we complete … … 185 200 $processed_count = 0; 186 201 $custom_processed = 0; 202 $brand_plugin_processed = 0; 187 203 $log_message = ''; 188 204 189 205 foreach ($product_ids as $product_id) { 190 206 $product = wc_get_product($product_id); 191 207 if (!$product) continue; 192 208 193 209 $newly_processed[] = $product_id; 194 210 $processed_count++; 195 196 $attrs = $product->get_attributes(); 197 198 // Get the raw product attributes 199 $raw_attributes = get_post_meta($product_id, '_product_attributes', true); 200 201 // First try to process it as a taxonomy attribute 202 if (isset($attrs[$this->core->get_option('source_taxonomy')])) { 203 $brand_ids = []; 204 205 // Check if this is a taxonomy attribute 206 if ($attrs[$this->core->get_option('source_taxonomy')]->is_taxonomy()) { 207 // Get term IDs from the attribute 208 $brand_ids = $attrs[$this->core->get_option('source_taxonomy')]->get_options(); 209 211 212 // Handle brand plugin taxonomies differently 213 if ($is_brand_plugin) { 214 // For brand plugins like Perfect Brands, get terms directly from taxonomy 215 $source_terms = get_the_terms($product_id, $source_taxonomy); 216 217 if ($source_terms && !is_wp_error($source_terms)) { 210 218 $new_brand_ids = []; 211 foreach ($brand_ids as $old_id) { 212 $term = get_term($old_id, $this->core->get_option('source_taxonomy')); 213 if ($term && !is_wp_error($term)) { 214 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 215 if ($new_term) { 216 $new_brand_ids[] = (int)$new_term->term_id; 217 } 219 220 foreach ($source_terms as $term) { 221 // Find corresponding term in destination taxonomy 222 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 223 if ($new_term) { 224 $new_brand_ids[] = (int)$new_term->term_id; 218 225 } 219 226 } 220 227 221 228 if (!empty($new_brand_ids)) { 222 229 // Store previous assignments for potential rollback 223 230 $this->core->get_backup()->backup_product_terms($product_id); 224 231 225 232 // Assign new terms 226 233 wp_set_object_terms($product_id, $new_brand_ids, $this->core->get_option('destination_taxonomy')); 234 $brand_plugin_processed++; 235 236 $this->core->add_debug("Brand plugin product processed", [ 237 'product_id' => $product_id, 238 'source_taxonomy' => $source_taxonomy, 239 'source_terms' => wp_list_pluck($source_terms, 'name'), 240 'new_term_ids' => $new_brand_ids 241 ]); 227 242 } 228 } else { 229 // This is a custom attribute, not a taxonomy 230 $custom_processed++; 231 232 // Get the value from the attribute 233 if (isset($raw_attributes[$this->core->get_option('source_taxonomy')]) && 234 isset($raw_attributes[$this->core->get_option('source_taxonomy')]['value'])) { 235 236 $brand_value = $raw_attributes[$this->core->get_option('source_taxonomy')]['value']; 237 238 // Try to find a term with this ID first (common case) 239 $term = get_term($brand_value, $this->core->get_option('source_taxonomy')); 240 241 if ($term && !is_wp_error($term)) { 242 // We found a matching term by ID 243 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 244 if ($new_term) { 245 // Store previous assignments for potential rollback 246 $this->core->get_backup()->backup_product_terms($product_id); 247 248 // Assign new term 249 wp_set_object_terms($product_id, [(int)$new_term->term_id], $this->core->get_option('destination_taxonomy')); 250 251 $this->core->add_debug("Custom attribute processed using term ID", [ 252 'product_id' => $product_id, 253 'brand_value' => $brand_value, 254 'term_id' => $term->term_id, 255 'term_name' => $term->name, 256 'new_term_id' => $new_term->term_id 257 ]); 243 } 244 } else { 245 // Original logic for WooCommerce attributes 246 $attrs = $product->get_attributes(); 247 248 // Get the raw product attributes 249 $raw_attributes = get_post_meta($product_id, '_product_attributes', true); 250 251 // First try to process it as a taxonomy attribute 252 if (isset($attrs[$source_taxonomy])) { 253 $brand_ids = []; 254 255 // Check if this is a taxonomy attribute 256 if ($attrs[$source_taxonomy]->is_taxonomy()) { 257 // Get term IDs from the attribute 258 $brand_ids = $attrs[$source_taxonomy]->get_options(); 259 260 $new_brand_ids = []; 261 foreach ($brand_ids as $old_id) { 262 $term = get_term($old_id, $source_taxonomy); 263 if ($term && !is_wp_error($term)) { 264 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 265 if ($new_term) { 266 $new_brand_ids[] = (int)$new_term->term_id; 267 } 258 268 } 259 } else { 260 // If we couldn't find by ID, treat it as a name or slug 261 $new_term = get_term_by('name', $brand_value, $this->core->get_option('destination_taxonomy')); 262 263 if (!$new_term) { 264 // Try creating the term 265 $result = wp_insert_term($brand_value, $this->core->get_option('destination_taxonomy')); 266 if (!is_wp_error($result)) { 267 $new_term_id = $result['term_id']; 268 269 } 270 271 if (!empty($new_brand_ids)) { 272 // Store previous assignments for potential rollback 273 $this->core->get_backup()->backup_product_terms($product_id); 274 275 // Assign new terms 276 wp_set_object_terms($product_id, $new_brand_ids, $this->core->get_option('destination_taxonomy')); 277 } 278 } else { 279 // This is a custom attribute, not a taxonomy 280 $custom_processed++; 281 282 // Get the value from the attribute 283 if (isset($raw_attributes[$source_taxonomy]) && 284 isset($raw_attributes[$source_taxonomy]['value'])) { 285 286 $brand_value = $raw_attributes[$source_taxonomy]['value']; 287 288 // Try to find a term with this ID first (common case) 289 $term = get_term($brand_value, $source_taxonomy); 290 291 if ($term && !is_wp_error($term)) { 292 // We found a matching term by ID 293 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 294 if ($new_term) { 269 295 // Store previous assignments for potential rollback 270 296 $this->core->get_backup()->backup_product_terms($product_id); 271 297 272 298 // Assign new term 273 wp_set_object_terms($product_id, [ $new_term_id], $this->core->get_option('destination_taxonomy'));274 275 $this->core->add_debug("Custom attribute processed by creating new term", [299 wp_set_object_terms($product_id, [(int)$new_term->term_id], $this->core->get_option('destination_taxonomy')); 300 301 $this->core->add_debug("Custom attribute processed using term ID", [ 276 302 'product_id' => $product_id, 277 303 'brand_value' => $brand_value, 278 'new_term_id' => $new_term_id 279 ]); 280 } else { 281 $this->core->add_debug("Error creating term from custom attribute", [ 282 'product_id' => $product_id, 283 'brand_value' => $brand_value, 284 'error' => $result->get_error_message() 304 'term_id' => $term->term_id, 305 'term_name' => $term->name, 306 'new_term_id' => $new_term->term_id 285 307 ]); 286 308 } 287 309 } else { 288 // Store previous assignments for potential rollback 289 $this->core->get_backup()->backup_product_terms($product_id); 290 291 // Assign existing term 292 wp_set_object_terms($product_id, [(int)$new_term->term_id], $this->core->get_option('destination_taxonomy')); 293 294 $this->core->add_debug("Custom attribute processed using existing term", [ 295 'product_id' => $product_id, 296 'brand_value' => $brand_value, 297 'new_term_id' => $new_term->term_id, 298 'new_term_name' => $new_term->name 299 ]); 310 // If we couldn't find by ID, treat it as a name or slug 311 $new_term = get_term_by('name', $brand_value, $this->core->get_option('destination_taxonomy')); 312 313 if (!$new_term) { 314 // Try creating the term 315 $result = wp_insert_term($brand_value, $this->core->get_option('destination_taxonomy')); 316 if (!is_wp_error($result)) { 317 $new_term_id = $result['term_id']; 318 319 // Store previous assignments for potential rollback 320 $this->core->get_backup()->backup_product_terms($product_id); 321 322 // Assign new term 323 wp_set_object_terms($product_id, [$new_term_id], $this->core->get_option('destination_taxonomy')); 324 325 $this->core->add_debug("Custom attribute processed by creating new term", [ 326 'product_id' => $product_id, 327 'brand_value' => $brand_value, 328 'new_term_id' => $new_term_id 329 ]); 330 } else { 331 $this->core->add_debug("Error creating term from custom attribute", [ 332 'product_id' => $product_id, 333 'brand_value' => $brand_value, 334 'error' => $result->get_error_message() 335 ]); 336 } 337 } else { 338 // Store previous assignments for potential rollback 339 $this->core->get_backup()->backup_product_terms($product_id); 340 341 // Assign existing term 342 wp_set_object_terms($product_id, [(int)$new_term->term_id], $this->core->get_option('destination_taxonomy')); 343 344 $this->core->add_debug("Custom attribute processed using existing term", [ 345 'product_id' => $product_id, 346 'brand_value' => $brand_value, 347 'new_term_id' => $new_term->term_id, 348 'new_term_name' => $new_term->name 349 ]); 350 } 300 351 } 301 352 } … … 304 355 } 305 356 } 306 357 307 358 // Update the list of processed products 308 359 $processed_products = array_merge($processed_products, $newly_processed); 309 360 update_option('tbfw_brands_processed_ids', $processed_products); 310 361 311 362 // Calculate overall progress 312 363 $processed_total = count($processed_products); 313 364 $percent = min(95, 45 + round(($processed_total / ($total + 1)) * 50)); 314 315 $log_message = "Processed {$processed_count} products in this batch ({$custom_processed} with custom attributes). Total processed: {$processed_total} of {$total}"; 365 366 // Build log message based on processing type 367 if ($is_brand_plugin) { 368 $log_message = "Processed {$processed_count} products from brand plugin ({$brand_plugin_processed} transferred). Total processed: {$processed_total} of {$total}"; 369 } else { 370 $log_message = "Processed {$processed_count} products in this batch ({$custom_processed} with custom attributes). Total processed: {$processed_total} of {$total}"; 371 } 316 372 317 373 return [ … … 632 688 ) 633 689 ); 634 690 635 691 if ($attachment_id) { 636 692 return (int)$attachment_id; 637 693 } 638 694 } 639 695 640 696 return false; 641 697 } 698 699 /** 700 * Get products from a brand plugin taxonomy 701 * 702 * @since 2.8.5 703 * @param string $taxonomy The brand plugin taxonomy (e.g., pwb-brand) 704 * @param array $exclude_ids Product IDs to exclude (already processed) 705 * @return array Array of product IDs 706 */ 707 private function get_brand_plugin_products($taxonomy, $exclude_ids = []) { 708 global $wpdb; 709 710 $batch_size = $this->core->get_batch_size(); 711 712 // Build query to get products with this taxonomy 713 $query = "SELECT DISTINCT tr.object_id 714 FROM {$wpdb->term_relationships} tr 715 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id 716 INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID 717 WHERE tt.taxonomy = %s 718 AND p.post_type = 'product' 719 AND p.post_status = 'publish'"; 720 721 $query_args = [$taxonomy]; 722 723 // Exclude already processed products 724 if (!empty($exclude_ids)) { 725 $placeholders = implode(',', array_fill(0, count($exclude_ids), '%d')); 726 $query .= " AND tr.object_id NOT IN ($placeholders)"; 727 $query_args = array_merge($query_args, $exclude_ids); 728 } 729 730 $query .= " ORDER BY tr.object_id ASC LIMIT %d"; 731 $query_args[] = $batch_size; 732 733 return $wpdb->get_col($wpdb->prepare($query, $query_args)); 734 } 735 736 /** 737 * Count total products with a brand plugin taxonomy 738 * 739 * @since 2.8.5 740 * @param string $taxonomy The brand plugin taxonomy 741 * @return int Total number of products 742 */ 743 private function count_brand_plugin_products($taxonomy) { 744 global $wpdb; 745 746 return (int) $wpdb->get_var( 747 $wpdb->prepare( 748 "SELECT COUNT(DISTINCT tr.object_id) 749 FROM {$wpdb->term_relationships} tr 750 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id 751 INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID 752 WHERE tt.taxonomy = %s 753 AND p.post_type = 'product' 754 AND p.post_status = 'publish'", 755 $taxonomy 756 ) 757 ); 758 } 642 759 } -
transfer-brands-for-woocommerce/tags/2.8.5/includes/class-utils.php
r3408293 r3408329 57 57 /** 58 58 * Count products with source brand - Improved version for both taxonomy and custom attributes 59 * 59 * 60 * @since 2.8.5 Added support for brand plugin taxonomies (pwb-brand, etc.) 60 61 * @return int Number of products 61 62 */ 62 63 public function count_products_with_source() { 63 64 global $wpdb; 64 65 65 66 $source_taxonomy = $this->core->get_option('source_taxonomy'); 66 67 // Get all products with this attribute, regardless of whether it's a taxonomy or custom attribute 68 $count = $wpdb->get_var( 69 $wpdb->prepare( 70 "SELECT COUNT(DISTINCT post_id) 71 FROM {$wpdb->postmeta} 72 WHERE meta_key = '_product_attributes' 73 AND meta_value LIKE %s 74 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 75 '%' . $wpdb->esc_like($source_taxonomy) . '%' 76 ) 77 ); 78 67 68 // Check if this is a brand plugin taxonomy (not a WooCommerce attribute) 69 if ($this->is_brand_plugin_taxonomy($source_taxonomy)) { 70 // For brand plugin taxonomies, count products using the taxonomy relationship 71 $count = $wpdb->get_var( 72 $wpdb->prepare( 73 "SELECT COUNT(DISTINCT tr.object_id) 74 FROM {$wpdb->term_relationships} tr 75 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id 76 INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID 77 WHERE tt.taxonomy = %s 78 AND p.post_type = 'product' 79 AND p.post_status = 'publish'", 80 $source_taxonomy 81 ) 82 ); 83 } else { 84 // For WooCommerce attributes, use the _product_attributes meta query 85 $count = $wpdb->get_var( 86 $wpdb->prepare( 87 "SELECT COUNT(DISTINCT post_id) 88 FROM {$wpdb->postmeta} 89 WHERE meta_key = '_product_attributes' 90 AND meta_value LIKE %s 91 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 92 '%' . $wpdb->esc_like($source_taxonomy) . '%' 93 ) 94 ); 95 } 96 79 97 // Log debug info 80 98 $this->core->add_debug("Product count for {$source_taxonomy}: {$count}", [ 81 99 'source_taxonomy' => $source_taxonomy, 82 ' sql' => $wpdb->last_query,100 'is_brand_plugin' => $this->is_brand_plugin_taxonomy($source_taxonomy), 83 101 'count' => $count 84 102 ]); 85 103 86 104 return $count; 105 } 106 107 /** 108 * Check if the given taxonomy is from a brand plugin (not a WooCommerce attribute) 109 * 110 * @since 2.8.5 111 * @param string $taxonomy The taxonomy to check 112 * @return bool True if it's a brand plugin taxonomy 113 */ 114 public function is_brand_plugin_taxonomy($taxonomy) { 115 // List of known brand plugin taxonomies 116 $brand_plugin_taxonomies = [ 117 'pwb-brand', // Perfect Brands for WooCommerce 118 'yith_product_brand', // YITH WooCommerce Brands 119 ]; 120 121 return in_array($taxonomy, $brand_plugin_taxonomies, true); 122 } 123 124 /** 125 * Get the image meta key for a brand plugin taxonomy 126 * 127 * @since 2.8.5 128 * @param string $taxonomy The taxonomy to get the image key for 129 * @return string|false The meta key or false if not a brand plugin 130 */ 131 public function get_brand_plugin_image_key($taxonomy) { 132 $image_keys = [ 133 'pwb-brand' => 'pwb_brand_image', 134 'yith_product_brand' => 'yith_woocommerce_brand_thumbnail_id', 135 ]; 136 137 return isset($image_keys[$taxonomy]) ? $image_keys[$taxonomy] : false; 87 138 } 88 139 -
transfer-brands-for-woocommerce/tags/2.8.5/readme.txt
r3408293 r3408329 4 4 Requires at least: 6.0 5 5 Tested up to: 6.8.2 6 Stable tag: 2.8. 46 Stable tag: 2.8.5 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 119 119 == Changelog == 120 120 121 = 2.8.5 = 122 * Added: Support for Perfect Brands for WooCommerce plugin (pwb-brand taxonomy) 123 * Added: Support for YITH WooCommerce Brands plugin (yith_product_brand taxonomy) 124 * Added: Automatic detection of third-party brand plugins in the source dropdown 125 * Added: Brand plugin taxonomies now shown in a separate "Brand Plugins" section in settings 126 * Improved: Analysis tool now properly displays brand plugin statistics 127 * Improved: Transfer logic handles taxonomy-to-taxonomy transfers for brand plugins 128 * Fixed: "Invalid taxonomy" error when using Perfect Brands for WooCommerce 129 121 130 = 2.8.4 = 122 131 * Added: Pre-transfer validation to check if WooCommerce Brands feature is enabled … … 236 245 == Upgrade Notice == 237 246 247 = 2.8.5 = 248 **New**: Now supports Perfect Brands for WooCommerce and YITH WooCommerce Brands! If you're using these popular brand plugins and want to migrate to WooCommerce's built-in Brands, this update makes it possible. Simply select your brand plugin's taxonomy from the dropdown and transfer. 249 238 250 = 2.8.4 = 239 251 **Important**: This update prevents a common issue where brands appear to transfer successfully but don't show in WooCommerce admin. The plugin now validates that WooCommerce Brands is properly enabled before allowing transfers, with clear instructions on how to enable it. -
transfer-brands-for-woocommerce/tags/2.8.5/transfer-brands-for-woocommerce.php
r3408293 r3408329 4 4 * Plugin URI: https://pluginatlas.com/transfer-brands-for-woocommerce 5 5 * Description: Official migration tool for WooCommerce 9.6 Brands. Safely transfer your product brand attributes to the new brand taxonomy with image support, batch processing, and full backup capabilities. 6 * Version: 2.8. 46 * Version: 2.8.5 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 7.4 … … 36 36 37 37 // Define plugin constants 38 define('TBFW_VERSION', '2.8. 4');38 define('TBFW_VERSION', '2.8.5'); 39 39 define('TBFW_PLUGIN_DIR', plugin_dir_path(__FILE__)); 40 40 define('TBFW_PLUGIN_URL', plugin_dir_url(__FILE__)); -
transfer-brands-for-woocommerce/trunk/includes/class-admin.php
r3408293 r3408329 303 303 * 304 304 * @since 2.3.0 305 * @since 2.8.5 Added support for Perfect Brands for WooCommerce (pwb-brand) 305 306 */ 306 307 public function source_taxonomy_callback() { 307 308 $attribute_taxonomies = wc_get_attribute_taxonomies(); 308 309 $source = $this->core->get_option('source_taxonomy', 'pa_brand'); 309 310 310 311 echo '<select name="tbfw_transfer_brands_options[source_taxonomy]">'; 311 312 foreach ($attribute_taxonomies as $tax) { 313 $tax_name = 'pa_' . $tax->attribute_name; 314 echo '<option value="' . esc_attr($tax_name) . '" ' . selected($tax_name, $source, false) . '>'; 315 echo esc_html($tax->attribute_label) . ' (' . esc_html($tax_name) . ')'; 316 echo '</option>'; 312 313 // WooCommerce Product Attributes section 314 if (!empty($attribute_taxonomies)) { 315 echo '<optgroup label="' . esc_attr__('WooCommerce Attributes', 'transfer-brands-for-woocommerce') . '">'; 316 foreach ($attribute_taxonomies as $tax) { 317 $tax_name = 'pa_' . $tax->attribute_name; 318 echo '<option value="' . esc_attr($tax_name) . '" ' . selected($tax_name, $source, false) . '>'; 319 echo esc_html($tax->attribute_label) . ' (' . esc_html($tax_name) . ')'; 320 echo '</option>'; 321 } 322 echo '</optgroup>'; 317 323 } 318 324 325 // Brand Plugins section - check for supported brand plugin taxonomies 326 $brand_plugins = $this->get_supported_brand_plugins(); 327 if (!empty($brand_plugins)) { 328 echo '<optgroup label="' . esc_attr__('Brand Plugins', 'transfer-brands-for-woocommerce') . '">'; 329 foreach ($brand_plugins as $plugin) { 330 echo '<option value="' . esc_attr($plugin['taxonomy']) . '" ' . selected($plugin['taxonomy'], $source, false) . '>'; 331 echo esc_html($plugin['label']); 332 echo '</option>'; 333 } 334 echo '</optgroup>'; 335 } 336 319 337 echo '</select>'; 320 echo '<p class="description">' . esc_html__('Select the source attribute that contains your brands.', 'transfer-brands-for-woocommerce') . '</p>'; 338 echo '<p class="description">' . esc_html__('Select the source attribute or taxonomy that contains your brands.', 'transfer-brands-for-woocommerce') . '</p>'; 339 340 // Show info about detected brand plugins 341 if (!empty($brand_plugins)) { 342 echo '<p class="description" style="color: #2271b1;"><span class="dashicons dashicons-info" style="font-size: 14px; width: 14px; height: 14px;"></span> '; 343 echo esc_html__('Detected brand plugin(s):', 'transfer-brands-for-woocommerce') . ' '; 344 $plugin_names = array_column($brand_plugins, 'name'); 345 echo '<strong>' . esc_html(implode(', ', $plugin_names)) . '</strong>'; 346 echo '</p>'; 347 } 348 } 349 350 /** 351 * Get list of supported brand plugins that are active 352 * 353 * @since 2.8.5 354 * @return array Array of supported brand plugins with their taxonomies 355 */ 356 private function get_supported_brand_plugins() { 357 $plugins = []; 358 359 // Perfect Brands for WooCommerce 360 if (taxonomy_exists('pwb-brand')) { 361 $plugins[] = [ 362 'taxonomy' => 'pwb-brand', 363 'name' => 'Perfect Brands for WooCommerce', 364 'label' => __('Perfect Brands (pwb-brand)', 'transfer-brands-for-woocommerce'), 365 'image_meta_key' => 'pwb_brand_image' 366 ]; 367 } 368 369 // YITH WooCommerce Brands (uses yith_product_brand taxonomy) 370 if (taxonomy_exists('yith_product_brand')) { 371 $plugins[] = [ 372 'taxonomy' => 'yith_product_brand', 373 'name' => 'YITH WooCommerce Brands', 374 'label' => __('YITH Brands (yith_product_brand)', 'transfer-brands-for-woocommerce'), 375 'image_meta_key' => 'yith_woocommerce_brand_thumbnail_id' 376 ]; 377 } 378 379 return $plugins; 321 380 } 322 381 -
transfer-brands-for-woocommerce/trunk/includes/class-ajax.php
r3408293 r3408329 134 134 /** 135 135 * AJAX handler for checking brands 136 * 137 * @since 2.8.5 Added support for brand plugin taxonomies (pwb-brand, etc.) 136 138 */ 137 139 public function ajax_check_brands() { 138 140 check_ajax_referer('tbfw_transfer_brands_nonce', 'nonce'); 139 140 if (!current_user_can('manage_woocommerce')) { 141 wp_die(__('You do not have permission to perform this action.', 'transfer-brands-for-woocommerce')); 142 } 143 141 142 if (!current_user_can('manage_woocommerce')) { 143 wp_die(__('You do not have permission to perform this action.', 'transfer-brands-for-woocommerce')); 144 } 145 146 $source_taxonomy = $this->core->get_option('source_taxonomy'); 147 $is_brand_plugin = $this->core->get_utils()->is_brand_plugin_taxonomy($source_taxonomy); 148 144 149 $source_terms = get_terms([ 145 'taxonomy' => $ this->core->get_option('source_taxonomy'),150 'taxonomy' => $source_taxonomy, 146 151 'hide_empty' => false 147 152 ]); 148 153 149 154 if (is_wp_error($source_terms)) { 150 155 wp_send_json_error(['message' => 'Error: ' . $source_terms->get_error_message()]); 151 156 return; 152 157 } 153 158 154 159 global $wpdb; 155 156 // Get info about custom attributes 157 $custom_attribute_count = $wpdb->get_var( 158 $wpdb->prepare( 159 "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} 160 WHERE meta_key = '_product_attributes' 161 AND meta_value LIKE %s 162 AND meta_value LIKE %s 163 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 164 '%' . $wpdb->esc_like($this->core->get_option('source_taxonomy')) . '%', 165 '%"is_taxonomy";i:0;%' 166 ) 167 ); 168 169 // Sample of products with custom attributes 170 $custom_products = $wpdb->get_results( 171 $wpdb->prepare( 172 "SELECT post_id, meta_value FROM {$wpdb->postmeta} 173 WHERE meta_key = '_product_attributes' 174 AND meta_value LIKE %s 175 AND meta_value LIKE %s 176 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish') 177 LIMIT 10", 178 '%' . $wpdb->esc_like($this->core->get_option('source_taxonomy')) . '%', 179 '%"is_taxonomy";i:0;%' 180 ) 181 ); 182 160 161 $custom_attribute_count = 0; 183 162 $custom_samples = []; 184 foreach ($custom_products as $item) { 185 $attributes = maybe_unserialize($item->meta_value); 186 if (is_array($attributes) && isset($attributes[$this->core->get_option('source_taxonomy')])) { 187 $product = wc_get_product($item->post_id); 188 if ($product) { 189 $custom_samples[] = [ 190 'id' => $item->post_id, 191 'name' => $product->get_name(), 192 'value' => $attributes[$this->core->get_option('source_taxonomy')]['value'] ?? 'N/A' 193 ]; 163 $sample_products = []; 164 165 // For brand plugin taxonomies, skip custom attribute checks (they don't use _product_attributes) 166 if (!$is_brand_plugin) { 167 // Get info about custom attributes (only for WooCommerce attributes) 168 $custom_attribute_count = $wpdb->get_var( 169 $wpdb->prepare( 170 "SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} 171 WHERE meta_key = '_product_attributes' 172 AND meta_value LIKE %s 173 AND meta_value LIKE %s 174 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 175 '%' . $wpdb->esc_like($source_taxonomy) . '%', 176 '%"is_taxonomy";i:0;%' 177 ) 178 ); 179 180 // Sample of products with custom attributes 181 $custom_products = $wpdb->get_results( 182 $wpdb->prepare( 183 "SELECT post_id, meta_value FROM {$wpdb->postmeta} 184 WHERE meta_key = '_product_attributes' 185 AND meta_value LIKE %s 186 AND meta_value LIKE %s 187 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish') 188 LIMIT 10", 189 '%' . $wpdb->esc_like($source_taxonomy) . '%', 190 '%"is_taxonomy";i:0;%' 191 ) 192 ); 193 194 foreach ($custom_products as $item) { 195 $attributes = maybe_unserialize($item->meta_value); 196 if (is_array($attributes) && isset($attributes[$source_taxonomy])) { 197 $product = wc_get_product($item->post_id); 198 if ($product) { 199 $custom_samples[] = [ 200 'id' => $item->post_id, 201 'name' => $product->get_name(), 202 'value' => $attributes[$source_taxonomy]['value'] ?? 'N/A' 203 ]; 204 } 194 205 } 195 206 } 196 207 } 197 208 198 209 // Check if any terms already exist in destination 199 210 $conflicting_terms = []; 200 211 $terms_with_images = 0; 201 212 202 213 foreach ($source_terms as $term) { 203 214 $exists = term_exists($term->name, $this->core->get_option('destination_taxonomy')); … … 205 216 $conflicting_terms[] = $term->name; 206 217 } 207 218 208 219 // Check if term has image using the comprehensive detection method 209 220 $transfer_instance = $this->core->get_transfer(); … … 213 224 $method->setAccessible(true); 214 225 $image_id = $method->invoke($transfer_instance, $term->term_id); 215 226 216 227 if ($image_id) { 217 228 $terms_with_images++; 218 229 } 219 230 } 220 221 // Get some sample products with taxonomy attributes 222 $products_query = new WP_Query([ 223 'post_type' => 'product', 224 'posts_per_page' => 5, 225 'tax_query' => [ 226 [ 227 'taxonomy' => $this->core->get_option('source_taxonomy'), 228 'operator' => 'EXISTS', 231 232 // Get sample products - different logic for brand plugins vs WooCommerce attributes 233 if ($is_brand_plugin) { 234 // For brand plugins, query products via taxonomy relationship 235 $products_query = new WP_Query([ 236 'post_type' => 'product', 237 'posts_per_page' => 5, 238 'post_status' => 'publish', 239 'tax_query' => [ 240 [ 241 'taxonomy' => $source_taxonomy, 242 'operator' => 'EXISTS', 243 ] 229 244 ] 230 ] 231 ]); 232 233 $sample_products = []; 234 235 if ($products_query->have_posts()) { 236 foreach ($products_query->posts as $post) { 237 $product = wc_get_product($post->ID); 238 $attrs = $product->get_attributes(); 239 240 if (isset($attrs[$this->core->get_option('source_taxonomy')])) { 241 $terms = []; 242 foreach ($attrs[$this->core->get_option('source_taxonomy')]->get_options() as $term_id) { 243 $term = get_term($term_id, $this->core->get_option('source_taxonomy')); 244 if ($term && !is_wp_error($term)) { 245 $terms[] = $term->name; 246 } 245 ]); 246 247 if ($products_query->have_posts()) { 248 foreach ($products_query->posts as $post) { 249 $product = wc_get_product($post->ID); 250 if (!$product) continue; 251 252 // Get terms directly from taxonomy 253 $product_terms = get_the_terms($post->ID, $source_taxonomy); 254 $term_names = []; 255 256 if ($product_terms && !is_wp_error($product_terms)) { 257 $term_names = wp_list_pluck($product_terms, 'name'); 247 258 } 248 259 249 260 $sample_products[] = [ 250 261 'id' => $post->ID, 251 262 'name' => $product->get_name(), 252 'brands' => $term s263 'brands' => $term_names 253 264 ]; 254 265 } 255 266 } 256 } 257 267 } else { 268 // For WooCommerce attributes, use the original query 269 $products_query = new WP_Query([ 270 'post_type' => 'product', 271 'posts_per_page' => 5, 272 'tax_query' => [ 273 [ 274 'taxonomy' => $source_taxonomy, 275 'operator' => 'EXISTS', 276 ] 277 ] 278 ]); 279 280 if ($products_query->have_posts()) { 281 foreach ($products_query->posts as $post) { 282 $product = wc_get_product($post->ID); 283 $attrs = $product->get_attributes(); 284 285 if (isset($attrs[$source_taxonomy])) { 286 $terms = []; 287 foreach ($attrs[$source_taxonomy]->get_options() as $term_id) { 288 $term = get_term($term_id, $source_taxonomy); 289 if ($term && !is_wp_error($term)) { 290 $terms[] = $term->name; 291 } 292 } 293 294 $sample_products[] = [ 295 'id' => $post->ID, 296 'name' => $product->get_name(), 297 'brands' => $terms 298 ]; 299 } 300 } 301 } 302 } 303 258 304 // Add debug info 259 305 $this->core->add_debug("Brand analysis performed", [ 260 306 'source_terms' => count($source_terms), 307 'is_brand_plugin' => $is_brand_plugin, 261 308 'custom_attribute_count' => $custom_attribute_count, 262 309 'taxonomy_samples' => count($sample_products), … … 297 344 298 345 $html .= '<h4>' . esc_html__('Source Brands Summary', 'transfer-brands-for-woocommerce') . '</h4>'; 299 $html .= '<ul>'; 300 $html .= '<li><strong>' . count($source_terms) . '</strong> ' . esc_html__('brands found in taxonomy', 'transfer-brands-for-woocommerce') . ' ' . esc_html($this->core->get_option('source_taxonomy')) . '</li>'; 301 $html .= '<li><strong>' . $custom_attribute_count . '</strong> ' . esc_html__('products have custom (non-taxonomy) attributes with name', 'transfer-brands-for-woocommerce') . ' ' . esc_html($this->core->get_option('source_taxonomy')) . '</li>'; 302 $html .= '<li><strong>' . $terms_with_images . '</strong> ' . esc_html__('brands have images that will be transferred', 'transfer-brands-for-woocommerce') . '</li>'; 303 $html .= '</ul>'; 304 305 // Warning about custom attributes 306 if ($custom_attribute_count > 0) { 346 347 // Show different info for brand plugins vs WooCommerce attributes 348 if ($is_brand_plugin) { 349 $html .= '<div class="notice notice-info inline" style="margin: 0 0 15px 0; padding: 10px 12px;">'; 350 $html .= '<p style="margin: 0;"><span class="dashicons dashicons-info" style="color: #2271b1;"></span> '; 351 $html .= '<strong>' . esc_html__('Brand Plugin Detected:', 'transfer-brands-for-woocommerce') . '</strong> '; 352 $html .= esc_html__('Transferring from a third-party brand plugin taxonomy.', 'transfer-brands-for-woocommerce'); 353 $html .= '</p></div>'; 354 355 // Get product count for brand plugin 356 $brand_plugin_product_count = $this->core->get_utils()->count_products_with_source(); 357 358 $html .= '<ul>'; 359 $html .= '<li><strong>' . count($source_terms) . '</strong> ' . esc_html__('brands found in taxonomy', 'transfer-brands-for-woocommerce') . ' <code>' . esc_html($source_taxonomy) . '</code></li>'; 360 $html .= '<li><strong>' . $brand_plugin_product_count . '</strong> ' . esc_html__('products have brands assigned', 'transfer-brands-for-woocommerce') . '</li>'; 361 $html .= '<li><strong>' . $terms_with_images . '</strong> ' . esc_html__('brands have images that will be transferred', 'transfer-brands-for-woocommerce') . '</li>'; 362 $html .= '</ul>'; 363 } else { 364 $html .= '<ul>'; 365 $html .= '<li><strong>' . count($source_terms) . '</strong> ' . esc_html__('brands found in taxonomy', 'transfer-brands-for-woocommerce') . ' <code>' . esc_html($source_taxonomy) . '</code></li>'; 366 $html .= '<li><strong>' . $custom_attribute_count . '</strong> ' . esc_html__('products have custom (non-taxonomy) attributes with name', 'transfer-brands-for-woocommerce') . ' <code>' . esc_html($source_taxonomy) . '</code></li>'; 367 $html .= '<li><strong>' . $terms_with_images . '</strong> ' . esc_html__('brands have images that will be transferred', 'transfer-brands-for-woocommerce') . '</li>'; 368 $html .= '</ul>'; 369 } 370 371 // Warning about custom attributes (only for WooCommerce attributes) 372 if (!$is_brand_plugin && $custom_attribute_count > 0) { 307 373 $html .= '<div class="notice notice-warning inline" style="margin-top: 15px;">'; 308 374 $html .= '<p><strong>Custom Attributes Detected:</strong> Some of your products use custom (non-taxonomy) attributes for brands.</p>'; … … 349 415 350 416 if (!empty($sample_products)) { 351 $html .= '<h4>Sample Taxonomy Products</h4>'; 352 $html .= '<p>Here are some products with taxonomy brand attributes:</p>'; 417 if ($is_brand_plugin) { 418 $html .= '<h4>' . esc_html__('Sample Products with Brand Plugin Brands', 'transfer-brands-for-woocommerce') . '</h4>'; 419 $html .= '<p>' . esc_html__('Here are some products with brands from the brand plugin:', 'transfer-brands-for-woocommerce') . '</p>'; 420 } else { 421 $html .= '<h4>' . esc_html__('Sample Products with Brand Attributes', 'transfer-brands-for-woocommerce') . '</h4>'; 422 $html .= '<p>' . esc_html__('Here are some products with taxonomy brand attributes:', 'transfer-brands-for-woocommerce') . '</p>'; 423 } 353 424 $html .= '<table class="widefat" style="margin-top: 10px;">'; 354 $html .= '<thead><tr><th>ID</th><th> Product</th><th>Current Brands</th></tr></thead>';425 $html .= '<thead><tr><th>ID</th><th>' . esc_html__('Product', 'transfer-brands-for-woocommerce') . '</th><th>' . esc_html__('Current Brands', 'transfer-brands-for-woocommerce') . '</th></tr></thead>'; 355 426 $html .= '<tbody>'; 356 427 357 428 foreach ($sample_products as $product) { 358 429 $html .= '<tr>'; 359 $html .= '<td>' . $product['id']. '</td>';430 $html .= '<td>' . esc_html($product['id']) . '</td>'; 360 431 $html .= '<td>' . esc_html($product['name']) . '</td>'; 361 432 $html .= '<td>' . esc_html(implode(', ', $product['brands'])) . '</td>'; 362 433 $html .= '</tr>'; 363 434 } 364 435 365 436 $html .= '</tbody></table>'; 366 437 } -
transfer-brands-for-woocommerce/trunk/includes/class-transfer.php
r3408293 r3408329 120 120 /** 121 121 * Process a batch of products 122 * 122 * 123 * @since 2.8.5 Added support for brand plugin taxonomies (pwb-brand, etc.) 123 124 * @return array Result data 124 125 */ 125 126 public function process_products_batch() { 126 127 global $wpdb; 127 128 129 $source_taxonomy = $this->core->get_option('source_taxonomy'); 130 $is_brand_plugin = $this->core->get_utils()->is_brand_plugin_taxonomy($source_taxonomy); 131 128 132 // Get products that have already been processed 129 133 $processed_products = get_option('tbfw_brands_processed_ids', []); 130 131 // Create exclusion placeholders for already processed products 132 $exclude_condition = ''; 133 $query_args = [ 134 '%' . $wpdb->esc_like($this->core->get_option('source_taxonomy')) . '%' 135 ]; 136 137 if (!empty($processed_products)) { 138 $placeholders = implode(',', array_fill(0, count($processed_products), '%d')); 139 $exclude_condition = " AND post_id NOT IN ($placeholders)"; 140 $query_args = array_merge($query_args, $processed_products); 141 } 142 143 $query_args[] = $this->core->get_batch_size(); 144 145 // Find products with brand attribute that haven't been processed yet 146 $query = "SELECT DISTINCT post_id 147 FROM {$wpdb->postmeta} 148 WHERE meta_key = '_product_attributes' 149 AND meta_value LIKE %s 150 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish') 151 {$exclude_condition} 152 LIMIT %d"; 153 154 $product_ids = $wpdb->get_col( 155 $wpdb->prepare($query, $query_args) 156 ); 157 158 // Count total products for progress calculation 159 $total = $wpdb->get_var( 160 $wpdb->prepare( 161 "SELECT COUNT(DISTINCT post_id) 162 FROM {$wpdb->postmeta} 163 WHERE meta_key = '_product_attributes' 164 AND meta_value LIKE %s 165 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 166 '%' . $wpdb->esc_like($this->core->get_option('source_taxonomy')) . '%' 167 ) 168 ); 169 $this->core->add_debug("Total products with source brand", ['total' => $total]); 134 135 // Different query logic for brand plugin taxonomies vs WooCommerce attributes 136 if ($is_brand_plugin) { 137 // For brand plugin taxonomies, query products via taxonomy relationship 138 $product_ids = $this->get_brand_plugin_products($source_taxonomy, $processed_products); 139 $total = $this->count_brand_plugin_products($source_taxonomy); 140 } else { 141 // For WooCommerce attributes, use the original _product_attributes meta query 142 $exclude_condition = ''; 143 $query_args = [ 144 '%' . $wpdb->esc_like($source_taxonomy) . '%' 145 ]; 146 147 if (!empty($processed_products)) { 148 $placeholders = implode(',', array_fill(0, count($processed_products), '%d')); 149 $exclude_condition = " AND post_id NOT IN ($placeholders)"; 150 $query_args = array_merge($query_args, $processed_products); 151 } 152 153 $query_args[] = $this->core->get_batch_size(); 154 155 // Find products with brand attribute that haven't been processed yet 156 $query = "SELECT DISTINCT post_id 157 FROM {$wpdb->postmeta} 158 WHERE meta_key = '_product_attributes' 159 AND meta_value LIKE %s 160 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish') 161 {$exclude_condition} 162 LIMIT %d"; 163 164 $product_ids = $wpdb->get_col( 165 $wpdb->prepare($query, $query_args) 166 ); 167 168 // Count total products for progress calculation 169 $total = $wpdb->get_var( 170 $wpdb->prepare( 171 "SELECT COUNT(DISTINCT post_id) 172 FROM {$wpdb->postmeta} 173 WHERE meta_key = '_product_attributes' 174 AND meta_value LIKE %s 175 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 176 '%' . $wpdb->esc_like($source_taxonomy) . '%' 177 ) 178 ); 179 } 180 181 $this->core->add_debug("Total products with source brand", [ 182 'total' => $total, 183 'is_brand_plugin' => $is_brand_plugin 184 ]); 170 185 171 186 // If there are no more products to process, we complete … … 185 200 $processed_count = 0; 186 201 $custom_processed = 0; 202 $brand_plugin_processed = 0; 187 203 $log_message = ''; 188 204 189 205 foreach ($product_ids as $product_id) { 190 206 $product = wc_get_product($product_id); 191 207 if (!$product) continue; 192 208 193 209 $newly_processed[] = $product_id; 194 210 $processed_count++; 195 196 $attrs = $product->get_attributes(); 197 198 // Get the raw product attributes 199 $raw_attributes = get_post_meta($product_id, '_product_attributes', true); 200 201 // First try to process it as a taxonomy attribute 202 if (isset($attrs[$this->core->get_option('source_taxonomy')])) { 203 $brand_ids = []; 204 205 // Check if this is a taxonomy attribute 206 if ($attrs[$this->core->get_option('source_taxonomy')]->is_taxonomy()) { 207 // Get term IDs from the attribute 208 $brand_ids = $attrs[$this->core->get_option('source_taxonomy')]->get_options(); 209 211 212 // Handle brand plugin taxonomies differently 213 if ($is_brand_plugin) { 214 // For brand plugins like Perfect Brands, get terms directly from taxonomy 215 $source_terms = get_the_terms($product_id, $source_taxonomy); 216 217 if ($source_terms && !is_wp_error($source_terms)) { 210 218 $new_brand_ids = []; 211 foreach ($brand_ids as $old_id) { 212 $term = get_term($old_id, $this->core->get_option('source_taxonomy')); 213 if ($term && !is_wp_error($term)) { 214 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 215 if ($new_term) { 216 $new_brand_ids[] = (int)$new_term->term_id; 217 } 219 220 foreach ($source_terms as $term) { 221 // Find corresponding term in destination taxonomy 222 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 223 if ($new_term) { 224 $new_brand_ids[] = (int)$new_term->term_id; 218 225 } 219 226 } 220 227 221 228 if (!empty($new_brand_ids)) { 222 229 // Store previous assignments for potential rollback 223 230 $this->core->get_backup()->backup_product_terms($product_id); 224 231 225 232 // Assign new terms 226 233 wp_set_object_terms($product_id, $new_brand_ids, $this->core->get_option('destination_taxonomy')); 234 $brand_plugin_processed++; 235 236 $this->core->add_debug("Brand plugin product processed", [ 237 'product_id' => $product_id, 238 'source_taxonomy' => $source_taxonomy, 239 'source_terms' => wp_list_pluck($source_terms, 'name'), 240 'new_term_ids' => $new_brand_ids 241 ]); 227 242 } 228 } else { 229 // This is a custom attribute, not a taxonomy 230 $custom_processed++; 231 232 // Get the value from the attribute 233 if (isset($raw_attributes[$this->core->get_option('source_taxonomy')]) && 234 isset($raw_attributes[$this->core->get_option('source_taxonomy')]['value'])) { 235 236 $brand_value = $raw_attributes[$this->core->get_option('source_taxonomy')]['value']; 237 238 // Try to find a term with this ID first (common case) 239 $term = get_term($brand_value, $this->core->get_option('source_taxonomy')); 240 241 if ($term && !is_wp_error($term)) { 242 // We found a matching term by ID 243 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 244 if ($new_term) { 245 // Store previous assignments for potential rollback 246 $this->core->get_backup()->backup_product_terms($product_id); 247 248 // Assign new term 249 wp_set_object_terms($product_id, [(int)$new_term->term_id], $this->core->get_option('destination_taxonomy')); 250 251 $this->core->add_debug("Custom attribute processed using term ID", [ 252 'product_id' => $product_id, 253 'brand_value' => $brand_value, 254 'term_id' => $term->term_id, 255 'term_name' => $term->name, 256 'new_term_id' => $new_term->term_id 257 ]); 243 } 244 } else { 245 // Original logic for WooCommerce attributes 246 $attrs = $product->get_attributes(); 247 248 // Get the raw product attributes 249 $raw_attributes = get_post_meta($product_id, '_product_attributes', true); 250 251 // First try to process it as a taxonomy attribute 252 if (isset($attrs[$source_taxonomy])) { 253 $brand_ids = []; 254 255 // Check if this is a taxonomy attribute 256 if ($attrs[$source_taxonomy]->is_taxonomy()) { 257 // Get term IDs from the attribute 258 $brand_ids = $attrs[$source_taxonomy]->get_options(); 259 260 $new_brand_ids = []; 261 foreach ($brand_ids as $old_id) { 262 $term = get_term($old_id, $source_taxonomy); 263 if ($term && !is_wp_error($term)) { 264 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 265 if ($new_term) { 266 $new_brand_ids[] = (int)$new_term->term_id; 267 } 258 268 } 259 } else { 260 // If we couldn't find by ID, treat it as a name or slug 261 $new_term = get_term_by('name', $brand_value, $this->core->get_option('destination_taxonomy')); 262 263 if (!$new_term) { 264 // Try creating the term 265 $result = wp_insert_term($brand_value, $this->core->get_option('destination_taxonomy')); 266 if (!is_wp_error($result)) { 267 $new_term_id = $result['term_id']; 268 269 } 270 271 if (!empty($new_brand_ids)) { 272 // Store previous assignments for potential rollback 273 $this->core->get_backup()->backup_product_terms($product_id); 274 275 // Assign new terms 276 wp_set_object_terms($product_id, $new_brand_ids, $this->core->get_option('destination_taxonomy')); 277 } 278 } else { 279 // This is a custom attribute, not a taxonomy 280 $custom_processed++; 281 282 // Get the value from the attribute 283 if (isset($raw_attributes[$source_taxonomy]) && 284 isset($raw_attributes[$source_taxonomy]['value'])) { 285 286 $brand_value = $raw_attributes[$source_taxonomy]['value']; 287 288 // Try to find a term with this ID first (common case) 289 $term = get_term($brand_value, $source_taxonomy); 290 291 if ($term && !is_wp_error($term)) { 292 // We found a matching term by ID 293 $new_term = get_term_by('name', $term->name, $this->core->get_option('destination_taxonomy')); 294 if ($new_term) { 269 295 // Store previous assignments for potential rollback 270 296 $this->core->get_backup()->backup_product_terms($product_id); 271 297 272 298 // Assign new term 273 wp_set_object_terms($product_id, [ $new_term_id], $this->core->get_option('destination_taxonomy'));274 275 $this->core->add_debug("Custom attribute processed by creating new term", [299 wp_set_object_terms($product_id, [(int)$new_term->term_id], $this->core->get_option('destination_taxonomy')); 300 301 $this->core->add_debug("Custom attribute processed using term ID", [ 276 302 'product_id' => $product_id, 277 303 'brand_value' => $brand_value, 278 'new_term_id' => $new_term_id 279 ]); 280 } else { 281 $this->core->add_debug("Error creating term from custom attribute", [ 282 'product_id' => $product_id, 283 'brand_value' => $brand_value, 284 'error' => $result->get_error_message() 304 'term_id' => $term->term_id, 305 'term_name' => $term->name, 306 'new_term_id' => $new_term->term_id 285 307 ]); 286 308 } 287 309 } else { 288 // Store previous assignments for potential rollback 289 $this->core->get_backup()->backup_product_terms($product_id); 290 291 // Assign existing term 292 wp_set_object_terms($product_id, [(int)$new_term->term_id], $this->core->get_option('destination_taxonomy')); 293 294 $this->core->add_debug("Custom attribute processed using existing term", [ 295 'product_id' => $product_id, 296 'brand_value' => $brand_value, 297 'new_term_id' => $new_term->term_id, 298 'new_term_name' => $new_term->name 299 ]); 310 // If we couldn't find by ID, treat it as a name or slug 311 $new_term = get_term_by('name', $brand_value, $this->core->get_option('destination_taxonomy')); 312 313 if (!$new_term) { 314 // Try creating the term 315 $result = wp_insert_term($brand_value, $this->core->get_option('destination_taxonomy')); 316 if (!is_wp_error($result)) { 317 $new_term_id = $result['term_id']; 318 319 // Store previous assignments for potential rollback 320 $this->core->get_backup()->backup_product_terms($product_id); 321 322 // Assign new term 323 wp_set_object_terms($product_id, [$new_term_id], $this->core->get_option('destination_taxonomy')); 324 325 $this->core->add_debug("Custom attribute processed by creating new term", [ 326 'product_id' => $product_id, 327 'brand_value' => $brand_value, 328 'new_term_id' => $new_term_id 329 ]); 330 } else { 331 $this->core->add_debug("Error creating term from custom attribute", [ 332 'product_id' => $product_id, 333 'brand_value' => $brand_value, 334 'error' => $result->get_error_message() 335 ]); 336 } 337 } else { 338 // Store previous assignments for potential rollback 339 $this->core->get_backup()->backup_product_terms($product_id); 340 341 // Assign existing term 342 wp_set_object_terms($product_id, [(int)$new_term->term_id], $this->core->get_option('destination_taxonomy')); 343 344 $this->core->add_debug("Custom attribute processed using existing term", [ 345 'product_id' => $product_id, 346 'brand_value' => $brand_value, 347 'new_term_id' => $new_term->term_id, 348 'new_term_name' => $new_term->name 349 ]); 350 } 300 351 } 301 352 } … … 304 355 } 305 356 } 306 357 307 358 // Update the list of processed products 308 359 $processed_products = array_merge($processed_products, $newly_processed); 309 360 update_option('tbfw_brands_processed_ids', $processed_products); 310 361 311 362 // Calculate overall progress 312 363 $processed_total = count($processed_products); 313 364 $percent = min(95, 45 + round(($processed_total / ($total + 1)) * 50)); 314 315 $log_message = "Processed {$processed_count} products in this batch ({$custom_processed} with custom attributes). Total processed: {$processed_total} of {$total}"; 365 366 // Build log message based on processing type 367 if ($is_brand_plugin) { 368 $log_message = "Processed {$processed_count} products from brand plugin ({$brand_plugin_processed} transferred). Total processed: {$processed_total} of {$total}"; 369 } else { 370 $log_message = "Processed {$processed_count} products in this batch ({$custom_processed} with custom attributes). Total processed: {$processed_total} of {$total}"; 371 } 316 372 317 373 return [ … … 632 688 ) 633 689 ); 634 690 635 691 if ($attachment_id) { 636 692 return (int)$attachment_id; 637 693 } 638 694 } 639 695 640 696 return false; 641 697 } 698 699 /** 700 * Get products from a brand plugin taxonomy 701 * 702 * @since 2.8.5 703 * @param string $taxonomy The brand plugin taxonomy (e.g., pwb-brand) 704 * @param array $exclude_ids Product IDs to exclude (already processed) 705 * @return array Array of product IDs 706 */ 707 private function get_brand_plugin_products($taxonomy, $exclude_ids = []) { 708 global $wpdb; 709 710 $batch_size = $this->core->get_batch_size(); 711 712 // Build query to get products with this taxonomy 713 $query = "SELECT DISTINCT tr.object_id 714 FROM {$wpdb->term_relationships} tr 715 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id 716 INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID 717 WHERE tt.taxonomy = %s 718 AND p.post_type = 'product' 719 AND p.post_status = 'publish'"; 720 721 $query_args = [$taxonomy]; 722 723 // Exclude already processed products 724 if (!empty($exclude_ids)) { 725 $placeholders = implode(',', array_fill(0, count($exclude_ids), '%d')); 726 $query .= " AND tr.object_id NOT IN ($placeholders)"; 727 $query_args = array_merge($query_args, $exclude_ids); 728 } 729 730 $query .= " ORDER BY tr.object_id ASC LIMIT %d"; 731 $query_args[] = $batch_size; 732 733 return $wpdb->get_col($wpdb->prepare($query, $query_args)); 734 } 735 736 /** 737 * Count total products with a brand plugin taxonomy 738 * 739 * @since 2.8.5 740 * @param string $taxonomy The brand plugin taxonomy 741 * @return int Total number of products 742 */ 743 private function count_brand_plugin_products($taxonomy) { 744 global $wpdb; 745 746 return (int) $wpdb->get_var( 747 $wpdb->prepare( 748 "SELECT COUNT(DISTINCT tr.object_id) 749 FROM {$wpdb->term_relationships} tr 750 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id 751 INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID 752 WHERE tt.taxonomy = %s 753 AND p.post_type = 'product' 754 AND p.post_status = 'publish'", 755 $taxonomy 756 ) 757 ); 758 } 642 759 } -
transfer-brands-for-woocommerce/trunk/includes/class-utils.php
r3408293 r3408329 57 57 /** 58 58 * Count products with source brand - Improved version for both taxonomy and custom attributes 59 * 59 * 60 * @since 2.8.5 Added support for brand plugin taxonomies (pwb-brand, etc.) 60 61 * @return int Number of products 61 62 */ 62 63 public function count_products_with_source() { 63 64 global $wpdb; 64 65 65 66 $source_taxonomy = $this->core->get_option('source_taxonomy'); 66 67 // Get all products with this attribute, regardless of whether it's a taxonomy or custom attribute 68 $count = $wpdb->get_var( 69 $wpdb->prepare( 70 "SELECT COUNT(DISTINCT post_id) 71 FROM {$wpdb->postmeta} 72 WHERE meta_key = '_product_attributes' 73 AND meta_value LIKE %s 74 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 75 '%' . $wpdb->esc_like($source_taxonomy) . '%' 76 ) 77 ); 78 67 68 // Check if this is a brand plugin taxonomy (not a WooCommerce attribute) 69 if ($this->is_brand_plugin_taxonomy($source_taxonomy)) { 70 // For brand plugin taxonomies, count products using the taxonomy relationship 71 $count = $wpdb->get_var( 72 $wpdb->prepare( 73 "SELECT COUNT(DISTINCT tr.object_id) 74 FROM {$wpdb->term_relationships} tr 75 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id 76 INNER JOIN {$wpdb->posts} p ON tr.object_id = p.ID 77 WHERE tt.taxonomy = %s 78 AND p.post_type = 'product' 79 AND p.post_status = 'publish'", 80 $source_taxonomy 81 ) 82 ); 83 } else { 84 // For WooCommerce attributes, use the _product_attributes meta query 85 $count = $wpdb->get_var( 86 $wpdb->prepare( 87 "SELECT COUNT(DISTINCT post_id) 88 FROM {$wpdb->postmeta} 89 WHERE meta_key = '_product_attributes' 90 AND meta_value LIKE %s 91 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish')", 92 '%' . $wpdb->esc_like($source_taxonomy) . '%' 93 ) 94 ); 95 } 96 79 97 // Log debug info 80 98 $this->core->add_debug("Product count for {$source_taxonomy}: {$count}", [ 81 99 'source_taxonomy' => $source_taxonomy, 82 ' sql' => $wpdb->last_query,100 'is_brand_plugin' => $this->is_brand_plugin_taxonomy($source_taxonomy), 83 101 'count' => $count 84 102 ]); 85 103 86 104 return $count; 105 } 106 107 /** 108 * Check if the given taxonomy is from a brand plugin (not a WooCommerce attribute) 109 * 110 * @since 2.8.5 111 * @param string $taxonomy The taxonomy to check 112 * @return bool True if it's a brand plugin taxonomy 113 */ 114 public function is_brand_plugin_taxonomy($taxonomy) { 115 // List of known brand plugin taxonomies 116 $brand_plugin_taxonomies = [ 117 'pwb-brand', // Perfect Brands for WooCommerce 118 'yith_product_brand', // YITH WooCommerce Brands 119 ]; 120 121 return in_array($taxonomy, $brand_plugin_taxonomies, true); 122 } 123 124 /** 125 * Get the image meta key for a brand plugin taxonomy 126 * 127 * @since 2.8.5 128 * @param string $taxonomy The taxonomy to get the image key for 129 * @return string|false The meta key or false if not a brand plugin 130 */ 131 public function get_brand_plugin_image_key($taxonomy) { 132 $image_keys = [ 133 'pwb-brand' => 'pwb_brand_image', 134 'yith_product_brand' => 'yith_woocommerce_brand_thumbnail_id', 135 ]; 136 137 return isset($image_keys[$taxonomy]) ? $image_keys[$taxonomy] : false; 87 138 } 88 139 -
transfer-brands-for-woocommerce/trunk/readme.txt
r3408293 r3408329 4 4 Requires at least: 6.0 5 5 Tested up to: 6.8.2 6 Stable tag: 2.8. 46 Stable tag: 2.8.5 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 119 119 == Changelog == 120 120 121 = 2.8.5 = 122 * Added: Support for Perfect Brands for WooCommerce plugin (pwb-brand taxonomy) 123 * Added: Support for YITH WooCommerce Brands plugin (yith_product_brand taxonomy) 124 * Added: Automatic detection of third-party brand plugins in the source dropdown 125 * Added: Brand plugin taxonomies now shown in a separate "Brand Plugins" section in settings 126 * Improved: Analysis tool now properly displays brand plugin statistics 127 * Improved: Transfer logic handles taxonomy-to-taxonomy transfers for brand plugins 128 * Fixed: "Invalid taxonomy" error when using Perfect Brands for WooCommerce 129 121 130 = 2.8.4 = 122 131 * Added: Pre-transfer validation to check if WooCommerce Brands feature is enabled … … 236 245 == Upgrade Notice == 237 246 247 = 2.8.5 = 248 **New**: Now supports Perfect Brands for WooCommerce and YITH WooCommerce Brands! If you're using these popular brand plugins and want to migrate to WooCommerce's built-in Brands, this update makes it possible. Simply select your brand plugin's taxonomy from the dropdown and transfer. 249 238 250 = 2.8.4 = 239 251 **Important**: This update prevents a common issue where brands appear to transfer successfully but don't show in WooCommerce admin. The plugin now validates that WooCommerce Brands is properly enabled before allowing transfers, with clear instructions on how to enable it. -
transfer-brands-for-woocommerce/trunk/transfer-brands-for-woocommerce.php
r3408293 r3408329 4 4 * Plugin URI: https://pluginatlas.com/transfer-brands-for-woocommerce 5 5 * Description: Official migration tool for WooCommerce 9.6 Brands. Safely transfer your product brand attributes to the new brand taxonomy with image support, batch processing, and full backup capabilities. 6 * Version: 2.8. 46 * Version: 2.8.5 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 7.4 … … 36 36 37 37 // Define plugin constants 38 define('TBFW_VERSION', '2.8. 4');38 define('TBFW_VERSION', '2.8.5'); 39 39 define('TBFW_PLUGIN_DIR', plugin_dir_path(__FILE__)); 40 40 define('TBFW_PLUGIN_URL', plugin_dir_url(__FILE__));
Note: See TracChangeset
for help on using the changeset viewer.