Plugin Directory

Changeset 3349241


Ignore:
Timestamp:
08/24/2025 12:21:05 PM (7 months ago)
Author:
multiwoomanager
Message:

Prepare release 1.1.9

Location:
multi-woo-manager/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • multi-woo-manager/trunk/README.txt

    r3303970 r3349241  
    66Tested up to: 6.7.1
    77Requires PHP: 8.0
    8 Stable tag: 1.1.8
     8Stable tag: 1.1.9
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    5757== Changelog ==
    5858
     59= 1.1.9 =
     60* Implemented product duplication functionality
     61
    5962= 1.1.8 =
    6063* 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  
    166166                // Only allow users who can delete posts (attachments) to perform this action.
    167167                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');
    168179            },
    169180        ));
     
    724735        return new WP_REST_Response(['media' => $media], 200);
    725736    }
    726    
     737
    727738    /**
    728739     * REST callback: Delete an attachment.
     
    774785        return new WP_REST_Response( $response, 200 );
    775786    }
     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    }
    7761096}
  • multi-woo-manager/trunk/multi-woo-manager.php

    r3303970 r3349241  
    1818 * Plugin URI:        https://multiwoomanager.com
    1919 * 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.8
     20 * Version:           1.1.9
    2121 * Author:            MultiWooManager
    2222 * Author URI:        https://multiwoomanager.com/
     
    3232}
    3333
    34 define( 'MULTI_WOO_MANAGER_VERSION', '1.1.8' );
     34define( 'MULTI_WOO_MANAGER_VERSION', '1.1.9' );
    3535
    3636/**
Note: See TracChangeset for help on using the changeset viewer.