Changeset 3349241
- Timestamp:
- 08/24/2025 12:21:05 PM (7 months ago)
- Location:
- multi-woo-manager/trunk
- Files:
-
- 3 edited
-
README.txt (modified) (2 diffs)
-
admin/class-multi-woo-manager-admin.php (modified) (3 diffs)
-
multi-woo-manager.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
multi-woo-manager/trunk/README.txt
r3303970 r3349241 6 6 Tested up to: 6.7.1 7 7 Requires PHP: 8.0 8 Stable tag: 1.1. 88 Stable tag: 1.1.9 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 57 57 == Changelog == 58 58 59 = 1.1.9 = 60 * Implemented product duplication functionality 61 59 62 = 1.1.8 = 60 63 * Fixed order of ACF fields to ensure they are displayed in the correct sequence. -
multi-woo-manager/trunk/admin/class-multi-woo-manager-admin.php
r3242765 r3349241 166 166 // Only allow users who can delete posts (attachments) to perform this action. 167 167 return current_user_can('delete_posts'); 168 }, 169 )); 170 171 /* Register the product duplication endpoint 172 * @since 1.2.0 173 */ 174 register_rest_route('wc/v3', '/mwm-duplicate-product/', array( 175 'methods' => 'POST', 176 'callback' => [$this, 'rest_mwm_duplicate_product'], 177 'permission_callback' => function () { 178 return current_user_can('edit_posts'); 168 179 }, 169 180 )); … … 724 735 return new WP_REST_Response(['media' => $media], 200); 725 736 } 726 737 727 738 /** 728 739 * REST callback: Delete an attachment. … … 774 785 return new WP_REST_Response( $response, 200 ); 775 786 } 787 788 /** 789 * REST callback: Duplicate a product with a new SKU, preserving all data including ACF and Yoast SEO meta 790 * 791 * @param WP_REST_Request $request The REST request. 792 * @return WP_REST_Response|WP_Error 793 */ 794 function rest_mwm_duplicate_product( $request ) { 795 $original_product_data = $request->get_param('original_product_data'); 796 $new_sku = $request->get_param('new_sku'); 797 798 if (empty($original_product_data) || empty($new_sku)) { 799 return new WP_Error( 800 'missing_parameters', 801 'Original product data and new SKU are required.', 802 array('status' => 400) 803 ); 804 } 805 806 // Ensure SKU is unique by appending a suffix if needed 807 $new_sku = $this->ensure_unique_sku($new_sku); 808 809 try { 810 // Prepare product data for duplication 811 $product_data = $original_product_data; 812 813 // Remove fields that shouldn't be duplicated 814 unset($product_data['id']); 815 unset($product_data['permalink']); 816 unset($product_data['date_created']); 817 unset($product_data['date_modified']); 818 unset($product_data['date_created_gmt']); 819 unset($product_data['date_modified_gmt']); 820 821 // Set the new SKU 822 $product_data['sku'] = $new_sku; 823 824 // Add "(Copy)" to the product name if not already present 825 if (isset($product_data['name']) && strpos($product_data['name'], '(Copy)') === false) { 826 $product_data['name'] = $product_data['name'] . ' (Copy)'; 827 } 828 829 // Set status to draft for the duplicated product 830 $product_data['status'] = 'draft'; 831 832 // Handle variations 833 $original_variations = []; 834 if (isset($product_data['variations']) && is_array($product_data['variations'])) { 835 $original_variations = $product_data['variations']; 836 unset($product_data['variations']); // We'll handle variations separately 837 } 838 839 // Clean meta_data - preserve ACF and Yoast SEO data but skip WordPress-specific meta 840 if (isset($product_data['meta_data']) && is_array($product_data['meta_data'])) { 841 $cleaned_meta_data = []; 842 foreach ($product_data['meta_data'] as $meta) { 843 // Skip WordPress-specific meta that shouldn't be duplicated 844 $skip_meta = [ 845 '_edit_lock', 846 '_edit_last', 847 '_wp_old_slug', 848 '_wp_old_date', 849 'total_sales', 850 '_wc_review_count', 851 '_wc_rating_count', 852 '_wc_average_rating' 853 ]; 854 855 if (!in_array($meta['key'], $skip_meta)) { 856 $cleaned_meta_data[] = $meta; 857 } 858 } 859 $product_data['meta_data'] = $cleaned_meta_data; 860 } 861 862 // Create the WooCommerce product using the WC_Product class 863 $product_type = $product_data['type'] ?? 'simple'; 864 $product = new WC_Product_Simple(); 865 866 if ($product_type === 'variable') { 867 $product = new WC_Product_Variable(); 868 } 869 870 // Set basic product properties 871 $product->set_name($product_data['name'] ?? ''); 872 $product->set_sku($new_sku); 873 $product->set_status('draft'); 874 $product->set_catalog_visibility($product_data['catalog_visibility'] ?? 'visible'); 875 $product->set_description($product_data['description'] ?? ''); 876 $product->set_short_description($product_data['short_description'] ?? ''); 877 $product->set_regular_price($product_data['regular_price'] ?? ''); 878 $product->set_sale_price($product_data['sale_price'] ?? ''); 879 $product->set_weight($product_data['weight'] ?? ''); 880 $product->set_manage_stock($product_data['manage_stock'] ?? false); 881 $product->set_stock_quantity($product_data['stock_quantity'] ?? null); 882 $product->set_stock_status($product_data['stock_status'] ?? 'instock'); 883 $product->set_virtual($product_data['virtual'] ?? false); 884 $product->set_downloadable($product_data['downloadable'] ?? false); 885 $product->set_tax_status($product_data['tax_status'] ?? 'taxable'); 886 $product->set_tax_class($product_data['tax_class'] ?? ''); 887 888 // Handle dimensions 889 if (isset($product_data['dimensions'])) { 890 $dimensions = $product_data['dimensions']; 891 $product->set_length($dimensions['length'] ?? ''); 892 $product->set_width($dimensions['width'] ?? ''); 893 $product->set_height($dimensions['height'] ?? ''); 894 } 895 896 // Handle categories 897 if (isset($product_data['categories']) && is_array($product_data['categories'])) { 898 $category_ids = array_map(function($cat) { 899 return is_array($cat) ? $cat['id'] : $cat; 900 }, $product_data['categories']); 901 $product->set_category_ids($category_ids); 902 } 903 904 // Handle tags 905 if (isset($product_data['tags']) && is_array($product_data['tags'])) { 906 $tag_ids = array_map(function($tag) { 907 return is_array($tag) ? $tag['id'] : $tag; 908 }, $product_data['tags']); 909 $product->set_tag_ids($tag_ids); 910 } 911 912 // Handle images 913 if (isset($product_data['images']) && is_array($product_data['images'])) { 914 $image_ids = []; 915 foreach ($product_data['images'] as $image) { 916 if (isset($image['id'])) { 917 $image_ids[] = $image['id']; 918 } 919 } 920 if (!empty($image_ids)) { 921 $product->set_image_id($image_ids[0]); // First image as featured 922 if (count($image_ids) > 1) { 923 $product->set_gallery_image_ids(array_slice($image_ids, 1)); 924 } 925 } 926 } 927 928 // Save the product 929 $new_product_id = $product->save(); 930 931 if (!$new_product_id) { 932 throw new Exception('Failed to create duplicated product'); 933 } 934 935 // Handle meta_data for ACF and Yoast SEO 936 if (isset($product_data['meta_data']) && is_array($product_data['meta_data'])) { 937 foreach ($product_data['meta_data'] as $meta) { 938 update_post_meta($new_product_id, $meta['key'], $meta['value']); 939 } 940 } 941 942 // Handle variations if the original product had any 943 if (!empty($original_variations) && is_array($original_variations)) { 944 foreach ($original_variations as $original_variation) { 945 $this->duplicate_variation($new_product_id, $original_variation, $new_sku); 946 } 947 } 948 949 // Get the full product data to return 950 $wc_product = wc_get_product($new_product_id); 951 $duplicated_product_data = $wc_product->get_data(); 952 $duplicated_product_data['id'] = $new_product_id; 953 $duplicated_product_data['sku'] = $new_sku; 954 955 // Trigger wp_update_post to ensure hooks fire and caches are cleared 956 wp_update_post(array('ID' => $new_product_id)); 957 958 // Prepare data for products table insertion 959 $products_table_data = array( 960 'woo_id' => $new_product_id, 961 'sku' => $new_sku, 962 'type' => $duplicated_product_data['type'], 963 'status' => $duplicated_product_data['status'], 964 'name' => $duplicated_product_data['name'], 965 'catalog_visibility' => $duplicated_product_data['catalog_visibility'], 966 'manage_stock' => $duplicated_product_data['manage_stock'] ? 'yes' : 'no', 967 'stock' => $duplicated_product_data['stock_quantity'] ?? 0, 968 'last_modified' => current_time('mysql'), 969 'has_variations' => ($duplicated_product_data['type'] === 'variable') ? count($original_variations) : 0, 970 ); 971 972 $response = array( 973 'success' => true, 974 'message' => 'Product duplicated successfully.', 975 'product' => $duplicated_product_data, 976 'products_table_data' => $products_table_data 977 ); 978 979 return new WP_REST_Response($response, 200); 980 981 } catch (Exception $e) { 982 error_log('Product duplication failed: ' . $e->getMessage()); 983 984 return new WP_Error( 985 'duplication_failed', 986 'Product duplication failed: ' . $e->getMessage(), 987 array('status' => 500) 988 ); 989 } 990 } 991 992 /** 993 * Ensure SKU is unique by appending a suffix if needed 994 * 995 * @param string $desired_sku The desired SKU 996 * @return string Unique SKU 997 */ 998 private function ensure_unique_sku($desired_sku) { 999 $original_sku = $desired_sku; 1000 $counter = 1; 1001 1002 // Check if the SKU already exists 1003 while (wc_get_product_id_by_sku($desired_sku)) { 1004 // If original SKU ends with "(Copy)", insert counter before it 1005 if (strpos($original_sku, '(Copy)') !== false) { 1006 $desired_sku = str_replace('(Copy)', "({$counter}) (Copy)", $original_sku); 1007 } else { 1008 // Otherwise just append counter 1009 $desired_sku = $original_sku . " ({$counter})"; 1010 } 1011 $counter++; 1012 1013 // Safety check to prevent infinite loop 1014 if ($counter > 1000) { 1015 $desired_sku = $original_sku . '-' . time(); 1016 break; 1017 } 1018 } 1019 1020 return $desired_sku; 1021 } 1022 1023 /** 1024 * Helper function to duplicate a variation 1025 * 1026 * @param int $new_product_id The ID of the newly created product 1027 * @param array $original_variation Original variation data 1028 * @param string $base_new_sku Base SKU for generating variation SKU 1029 */ 1030 private function duplicate_variation($new_product_id, $original_variation, $base_new_sku) { 1031 try { 1032 $variation = new WC_Product_Variation(); 1033 $variation->set_parent_id($new_product_id); 1034 1035 // Set basic variation properties with unique SKU 1036 $variation_sku = ''; 1037 if (!empty($original_variation['sku'])) { 1038 $variation_sku = $this->ensure_unique_sku($base_new_sku . '-' . substr($original_variation['sku'], -8)); 1039 } 1040 $variation->set_sku($variation_sku); 1041 $variation->set_status('draft'); 1042 $variation->set_regular_price($original_variation['regular_price'] ?? ''); 1043 $variation->set_sale_price($original_variation['sale_price'] ?? ''); 1044 $variation->set_weight($original_variation['weight'] ?? ''); 1045 $variation->set_manage_stock($original_variation['manage_stock'] ?? false); 1046 $variation->set_stock_quantity($original_variation['stock_quantity'] ?? null); 1047 $variation->set_stock_status($original_variation['stock_status'] ?? 'instock'); 1048 $variation->set_virtual($original_variation['virtual'] ?? false); 1049 $variation->set_downloadable($original_variation['downloadable'] ?? false); 1050 1051 // Handle dimensions 1052 if (isset($original_variation['dimensions'])) { 1053 $dimensions = $original_variation['dimensions']; 1054 $variation->set_length($dimensions['length'] ?? ''); 1055 $variation->set_width($dimensions['width'] ?? ''); 1056 $variation->set_height($dimensions['height'] ?? ''); 1057 } 1058 1059 // Handle attributes 1060 if (isset($original_variation['attributes']) && is_array($original_variation['attributes'])) { 1061 $variation->set_attributes($original_variation['attributes']); 1062 } 1063 1064 // Handle image 1065 if (isset($original_variation['image']) && isset($original_variation['image']['id'])) { 1066 $variation->set_image_id($original_variation['image']['id']); 1067 } 1068 1069 // Save the variation 1070 $new_variation_id = $variation->save(); 1071 1072 // Handle meta_data for ACF fields 1073 if (isset($original_variation['meta_data']) && is_array($original_variation['meta_data'])) { 1074 foreach ($original_variation['meta_data'] as $meta) { 1075 $skip_meta = [ 1076 '_edit_lock', 1077 '_edit_last', 1078 '_wp_old_slug', 1079 '_wp_old_date' 1080 ]; 1081 1082 if (!in_array($meta['key'], $skip_meta)) { 1083 update_post_meta($new_variation_id, $meta['key'], $meta['value']); 1084 } 1085 } 1086 } 1087 1088 return $new_variation_id; 1089 1090 } catch (Exception $e) { 1091 error_log('Variation duplication failed: ' . $e->getMessage()); 1092 // Continue with other variations even if one fails 1093 return false; 1094 } 1095 } 776 1096 } -
multi-woo-manager/trunk/multi-woo-manager.php
r3303970 r3349241 18 18 * Plugin URI: https://multiwoomanager.com 19 19 * Description: WooCommerce products management, the easy way. Plugin extends WooCommerce REST API in order to provide optimized API routes for product management. 20 * Version: 1.1. 820 * Version: 1.1.9 21 21 * Author: MultiWooManager 22 22 * Author URI: https://multiwoomanager.com/ … … 32 32 } 33 33 34 define( 'MULTI_WOO_MANAGER_VERSION', '1.1. 8' );34 define( 'MULTI_WOO_MANAGER_VERSION', '1.1.9' ); 35 35 36 36 /**
Note: See TracChangeset
for help on using the changeset viewer.