ソースを参照

studiou-wc-product-cat-manage: implements feature to moving products between categories

Dalibor Votruba 1 年間 前
コミット
922da8c490

+ 126 - 83
studiou-wc-product-cat-manage/assets/css/admin.css

@@ -1,84 +1,127 @@
-/**
- * Admin styles for Studiou WC Product Category Manager
- */
-
- .studiou-wcpcm-wrap {
-    max-width: 1200px;
-    margin: 20px 0;
-}
-
-.studiou-wcpcm-card {
-    background: #fff;
-    border: 1px solid #ccd0d4;
-    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
-    padding: 20px;
-    margin-bottom: 20px;
-    border-radius: 4px;
-}
-
-.studiou-wcpcm-notice-area {
-    margin-bottom: 20px;
-}
-
-.studiou-wcpcm-notice {
-    padding: 10px 15px;
-    margin: 0 0 15px;
-    border-left: 4px solid #00a0d2;
-    background: #fff;
-    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
-    position: relative;
-}
-
-.studiou-wcpcm-notice.studiou-wcpcm-notice-error {
-    border-left-color: #dc3232;
-}
-
-.studiou-wcpcm-notice.studiou-wcpcm-notice-success {
-    border-left-color: #46b450;
-}
-
-#studiou-wcpcm-import-form .form-field,
-#studiou-wcpcm-export-form .form-field {
-    margin-bottom: 15px;
-}
-
-#studiou-wcpcm-import-form label,
-#studiou-wcpcm-export-form label {
-    display: block;
-    margin-bottom: 5px;
-    font-weight: 600;
-}
-
-#studiou-wcpcm-import-form input[type="file"] {
-    padding: 8px 0;
-}
-
-.submit-buttons {
-    margin-top: 20px;
-    display: flex;
-    align-items: center;
-}
-
-.submit-buttons .spinner {
-    float: none;
-    margin-left: 10px;
-}
-
-.studiou-wcpcm-import-results,
-.studiou-wcpcm-export-results {
-    margin-top: 30px;
-    padding-top: 20px;
-    border-top: 1px solid #eee;
-}
-
-.studiou-wcpcm-import-results pre,
-.studiou-wcpcm-sample-csv pre {
-    background: #f6f7f7;
-    padding: 15px;
-    border: 1px solid #ddd;
-    border-radius: 3px;
-    overflow: auto;
-    font-family: monospace;
-    white-space: pre;
-    max-height: 300px;
+/**
+ * Admin styles for Studiou WC Product Category Manager
+ */
+
+ .studiou-wcpcm-wrap {
+    max-width: 1200px;
+    margin: 20px 0;
+}
+
+.studiou-wcpcm-card {
+    background: #fff;
+    border: 1px solid #ccd0d4;
+    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
+    padding: 20px;
+    margin-bottom: 20px;
+    border-radius: 4px;
+}
+
+.studiou-wcpcm-notice-area {
+    margin-bottom: 20px;
+}
+
+.studiou-wcpcm-notice {
+    padding: 10px 15px;
+    margin: 0 0 15px;
+    border-left: 4px solid #00a0d2;
+    background: #fff;
+    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
+    position: relative;
+}
+
+.studiou-wcpcm-notice.studiou-wcpcm-notice-error {
+    border-left-color: #dc3232;
+}
+
+.studiou-wcpcm-notice.studiou-wcpcm-notice-success {
+    border-left-color: #46b450;
+}
+
+#studiou-wcpcm-import-form .form-field,
+#studiou-wcpcm-export-form .form-field {
+    margin-bottom: 15px;
+}
+
+#studiou-wcpcm-import-form label,
+#studiou-wcpcm-export-form label {
+    display: block;
+    margin-bottom: 5px;
+    font-weight: 600;
+}
+
+#studiou-wcpcm-import-form input[type="file"] {
+    padding: 8px 0;
+}
+
+.submit-buttons {
+    margin-top: 20px;
+    display: flex;
+    align-items: center;
+}
+
+.submit-buttons .spinner {
+    float: none;
+    margin-left: 10px;
+}
+
+.studiou-wcpcm-import-results,
+.studiou-wcpcm-export-results,
+.studiou-wcpcm-move-results {
+    margin-top: 30px;
+    padding-top: 20px;
+    border-top: 1px solid #eee;
+}
+
+.studiou-wcpcm-import-results pre,
+.studiou-wcpcm-sample-csv pre {
+    background: #f6f7f7;
+    padding: 15px;
+    border: 1px solid #ddd;
+    border-radius: 3px;
+    overflow: auto;
+    font-family: monospace;
+    white-space: pre;
+    max-height: 300px;
+}
+
+/* Manipulations page specific styles */
+.form-table th {
+    width: 200px;
+    vertical-align: top;
+    padding-top: 15px;
+}
+
+.form-table td {
+    padding-bottom: 15px;
+}
+
+.form-table select.regular-text {
+    width: 300px;
+}
+
+.studiou-wcpcm-move-results {
+    background: #f9f9f9;
+    padding: 15px;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+}
+
+.studiou-wcpcm-move-results h3 {
+    margin-top: 0;
+    margin-bottom: 10px;
+}
+
+.studiou-wcpcm-move-results #studiou-wcpcm-move-message {
+    font-weight: 500;
+    color: #333;
+}
+
+/* Info notes styling */
+.studiou-wcpcm-card ul {
+    margin-left: 20px;
+}
+
+.studiou-wcpcm-card ul li {
+    margin-bottom: 8px;
+    line-height: 1.6;
 }

+ 240 - 171
studiou-wc-product-cat-manage/assets/js/admin.js

@@ -1,172 +1,241 @@
-/**
- * Admin JavaScript for Studiou WC Product Category Manager
- */
-(function($) {
-    'use strict';
-    
-    // Initialize admin scripts
-    $(document).ready(function() {
-        // Import form handling
-        if ($('#studiou-wcpcm-import-form').length) {
-            initImportForm();
-        }
-        
-        // Export form handling
-        if ($('#studiou-wcpcm-export-form').length) {
-            initExportForm();
-        }
-        
-        // Sample CSV download handler
-        if ($('#studiou-wcpcm-download-sample').length) {
-            initSampleDownload();
-        }
-    });
-    
-    /**
-     * Initialize import form
-     */
-    function initImportForm() {
-        $('#studiou-wcpcm-import-form').on('submit', function(e) {
-            e.preventDefault();
-            
-            // Show spinner
-            var $submitBtn = $(this).find('#studiou-wcpcm-import-button');
-            var $spinner = $(this).find('.spinner');
-            
-            $submitBtn.prop('disabled', true);
-            $spinner.css('visibility', 'visible');
-            
-            // Clear previous notices
-            $('.studiou-wcpcm-notice-area').empty();
-            
-            // Create form data
-            var formData = new FormData(this);
-            formData.append('action', 'studiou_wcpcm_import');
-            formData.append('nonce', studiouWcpcm.nonce);
-            
-            // Send AJAX request
-            $.ajax({
-                url: studiouWcpcm.ajaxUrl,
-                type: 'POST',
-                data: formData,
-                processData: false,
-                contentType: false,
-                success: function(response) {
-                    if (response.success) {
-                        showNotice('success', studiouWcpcm.i18n.importSuccess);
-                        
-                        // Show results
-                        $('#studiou-wcpcm-import-results').text(response.data.message);
-                        $('.studiou-wcpcm-import-results').show();
-                    } else {
-                        showNotice('error', response.data.message);
-                    }
-                },
-                error: function() {
-                    showNotice('error', studiouWcpcm.i18n.importError);
-                },
-                complete: function() {
-                    // Hide spinner
-                    $submitBtn.prop('disabled', false);
-                    $spinner.css('visibility', 'hidden');
-                }
-            });
-        });
-    }
-    
-    /**
-     * Initialize export form
-     */
-    function initExportForm() {
-        $('#studiou-wcpcm-export-form').on('submit', function(e) {
-            e.preventDefault();
-            
-            // Show spinner
-            var $submitBtn = $(this).find('#studiou-wcpcm-export-button');
-            var $spinner = $(this).find('.spinner');
-            
-            $submitBtn.prop('disabled', true);
-            $spinner.css('visibility', 'visible');
-            
-            // Clear previous notices
-            $('.studiou-wcpcm-notice-area').empty();
-            
-            // Send AJAX request
-            $.ajax({
-                url: studiouWcpcm.ajaxUrl,
-                type: 'POST',
-                data: {
-                    action: 'studiou_wcpcm_export',
-                    nonce: studiouWcpcm.nonce
-                },
-                success: function(response) {
-                    if (response.success) {
-                        showNotice('success', studiouWcpcm.i18n.exportSuccess);
-                        
-                        // Show download link
-                        $('#studiou-wcpcm-export-message').text(response.data.message);
-                        $('#studiou-wcpcm-download-export').attr('href', response.data.download_url);
-                        $('.studiou-wcpcm-export-results').show();
-                    } else {
-                        showNotice('error', response.data.message);
-                    }
-                },
-                error: function() {
-                    showNotice('error', studiouWcpcm.i18n.exportError);
-                },
-                complete: function() {
-                    // Hide spinner
-                    $submitBtn.prop('disabled', false);
-                    $spinner.css('visibility', 'hidden');
-                }
-            });
-        });
-    }
-    
-    /**
-     * Initialize sample CSV download
-     */
-    function initSampleDownload() {
-        $('#studiou-wcpcm-download-sample').on('click', function(e) {
-            e.preventDefault();
-            
-            // Sample CSV content
-            var csvContent = 'name,url-name,parent-name,description,display-type,visibility,protected-passwords,operation\n' +
-                '"Clothing","clothing","","Top quality clothing","default","public","","A"\n' +
-                '"Men","men","Clothing","Men\'s clothing","products","public","","A"\n' +
-                '"Women","women","Clothing","Women\'s clothing","products","protected","password123","A"\n' +
-                '"Kids","kids","Clothing","Kids clothing","products","public","","U"\n' +
-                '"Accessories","accessories","","All accessories","default","public","","D"';
-            
-            // Create download link
-            var blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
-            var url = URL.createObjectURL(blob);
-            var link = document.createElement('a');
-            
-            link.setAttribute('href', url);
-            link.setAttribute('download', 'sample-product-categories.csv');
-            link.style.visibility = 'hidden';
-            
-            document.body.appendChild(link);
-            link.click();
-            document.body.removeChild(link);
-        });
-    }
-    
-    /**
-     * Show notice message
-     * 
-     * @param {string} type Notice type (success, error)
-     * @param {string} message Notice message
-     */
-    function showNotice(type, message) {
-        var $notice = $('<div class="studiou-wcpcm-notice studiou-wcpcm-notice-' + type + '"></div>').text(message);
-        $('.studiou-wcpcm-notice-area').append($notice);
-        
-        // Scroll to notice
-        $('html, body').animate({
-            scrollTop: $('.studiou-wcpcm-notice-area').offset().top - 50
-        }, 500);
-    }
-    
+/**
+ * Admin JavaScript for Studiou WC Product Category Manager
+ */
+(function($) {
+    'use strict';
+    
+    // Initialize admin scripts
+    $(document).ready(function() {
+        // Import form handling
+        if ($('#studiou-wcpcm-import-form').length) {
+            initImportForm();
+        }
+        
+        // Export form handling
+        if ($('#studiou-wcpcm-export-form').length) {
+            initExportForm();
+        }
+        
+        // Move products form handling
+        if ($('#studiou-wcpcm-move-form').length) {
+            initMoveForm();
+        }
+        
+        // Sample CSV download handler
+        if ($('#studiou-wcpcm-download-sample').length) {
+            initSampleDownload();
+        }
+    });
+    
+    /**
+     * Initialize import form
+     */
+    function initImportForm() {
+        $('#studiou-wcpcm-import-form').on('submit', function(e) {
+            e.preventDefault();
+            
+            // Show spinner
+            var $submitBtn = $(this).find('#studiou-wcpcm-import-button');
+            var $spinner = $(this).find('.spinner');
+            
+            $submitBtn.prop('disabled', true);
+            $spinner.css('visibility', 'visible');
+            
+            // Clear previous notices
+            $('.studiou-wcpcm-notice-area').empty();
+            
+            // Create form data
+            var formData = new FormData(this);
+            formData.append('action', 'studiou_wcpcm_import');
+            formData.append('nonce', studiouWcpcm.nonce);
+            
+            // Send AJAX request
+            $.ajax({
+                url: studiouWcpcm.ajaxUrl,
+                type: 'POST',
+                data: formData,
+                processData: false,
+                contentType: false,
+                success: function(response) {
+                    if (response.success) {
+                        showNotice('success', studiouWcpcm.i18n.importSuccess);
+                        
+                        // Show results
+                        $('#studiou-wcpcm-import-results').text(response.data.message);
+                        $('.studiou-wcpcm-import-results').show();
+                    } else {
+                        showNotice('error', response.data.message);
+                    }
+                },
+                error: function() {
+                    showNotice('error', studiouWcpcm.i18n.importError);
+                },
+                complete: function() {
+                    // Hide spinner
+                    $submitBtn.prop('disabled', false);
+                    $spinner.css('visibility', 'hidden');
+                }
+            });
+        });
+    }
+    
+    /**
+     * Initialize export form
+     */
+    function initExportForm() {
+        $('#studiou-wcpcm-export-form').on('submit', function(e) {
+            e.preventDefault();
+            
+            // Show spinner
+            var $submitBtn = $(this).find('#studiou-wcpcm-export-button');
+            var $spinner = $(this).find('.spinner');
+            
+            $submitBtn.prop('disabled', true);
+            $spinner.css('visibility', 'visible');
+            
+            // Clear previous notices
+            $('.studiou-wcpcm-notice-area').empty();
+            
+            // Send AJAX request
+            $.ajax({
+                url: studiouWcpcm.ajaxUrl,
+                type: 'POST',
+                data: {
+                    action: 'studiou_wcpcm_export',
+                    nonce: studiouWcpcm.nonce
+                },
+                success: function(response) {
+                    if (response.success) {
+                        showNotice('success', studiouWcpcm.i18n.exportSuccess);
+                        
+                        // Show download link
+                        $('#studiou-wcpcm-export-message').text(response.data.message);
+                        $('#studiou-wcpcm-download-export').attr('href', response.data.download_url);
+                        $('.studiou-wcpcm-export-results').show();
+                    } else {
+                        showNotice('error', response.data.message);
+                    }
+                },
+                error: function() {
+                    showNotice('error', studiouWcpcm.i18n.exportError);
+                },
+                complete: function() {
+                    // Hide spinner
+                    $submitBtn.prop('disabled', false);
+                    $spinner.css('visibility', 'hidden');
+                }
+            });
+        });
+    }
+    
+    /**
+     * Initialize move products form
+     */
+    function initMoveForm() {
+        $('#studiou-wcpcm-move-form').on('submit', function(e) {
+            e.preventDefault();
+            
+            // Validate that different categories are selected
+            var sourceCategory = $('#source_category').val();
+            var targetCategory = $('#target_category').val();
+            
+            if (sourceCategory === targetCategory) {
+                showNotice('error', studiouWcpcm.i18n.selectDifferentCategories);
+                return;
+            }
+            
+            // Show spinner
+            var $submitBtn = $(this).find('#studiou-wcpcm-move-button');
+            var $spinner = $(this).find('.spinner');
+            
+            $submitBtn.prop('disabled', true);
+            $spinner.css('visibility', 'visible');
+            
+            // Clear previous notices
+            $('.studiou-wcpcm-notice-area').empty();
+            
+            // Send AJAX request
+            $.ajax({
+                url: studiouWcpcm.ajaxUrl,
+                type: 'POST',
+                data: {
+                    action: 'studiou_wcpcm_move_products',
+                    source_category: sourceCategory,
+                    target_category: targetCategory,
+                    nonce: studiouWcpcm.nonce
+                },
+                success: function(response) {
+                    if (response.success) {
+                        showNotice('success', studiouWcpcm.i18n.moveSuccess);
+                        
+                        // Show results
+                        $('#studiou-wcpcm-move-message').html('<p>' + response.data.message + '</p>');
+                        $('.studiou-wcpcm-move-results').show();
+                        
+                        // Refresh the page to update category counts
+                        setTimeout(function() {
+                            location.reload();
+                        }, 3000);
+                    } else {
+                        showNotice('error', response.data.message);
+                    }
+                },
+                error: function() {
+                    showNotice('error', studiouWcpcm.i18n.moveError);
+                },
+                complete: function() {
+                    // Hide spinner
+                    $submitBtn.prop('disabled', false);
+                    $spinner.css('visibility', 'hidden');
+                }
+            });
+        });
+    }
+    
+    /**
+     * Initialize sample CSV download
+     */
+    function initSampleDownload() {
+        $('#studiou-wcpcm-download-sample').on('click', function(e) {
+            e.preventDefault();
+            
+            // Sample CSV content
+            var csvContent = 'name,url-name,parent-name,description,display-type,visibility,protected-passwords,operation\n' +
+                '"Clothing","clothing","","Top quality clothing","default","public","","A"\n' +
+                '"Men","men","Clothing","Men\'s clothing","products","public","","A"\n' +
+                '"Women","women","Clothing","Women\'s clothing","products","protected","password123","A"\n' +
+                '"Kids","kids","Clothing","Kids clothing","products","public","","U"\n' +
+                '"Accessories","accessories","","All accessories","default","public","","D"';
+            
+            // Create download link
+            var blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
+            var url = URL.createObjectURL(blob);
+            var link = document.createElement('a');
+            
+            link.setAttribute('href', url);
+            link.setAttribute('download', 'sample-product-categories.csv');
+            link.style.visibility = 'hidden';
+            
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+        });
+    }
+    
+    /**
+     * Show notice message
+     * 
+     * @param {string} type Notice type (success, error)
+     * @param {string} message Notice message
+     */
+    function showNotice(type, message) {
+        var $notice = $('<div class="studiou-wcpcm-notice studiou-wcpcm-notice-' + type + '"></div>').text(message);
+        $('.studiou-wcpcm-notice-area').append($notice);
+        
+        // Scroll to notice
+        $('html, body').animate({
+            scrollTop: $('.studiou-wcpcm-notice-area').offset().top - 50
+        }, 500);
+    }
+    
 })(jQuery);

+ 4 - 2
studiou-wc-product-cat-manage/directory-structure.txt

@@ -7,9 +7,11 @@ studiou-wc-product-cat-manage/
 ├── includes/
 │   ├── class-studiou-wc-product-cat-manage-db.php
 │   ├── class-studiou-wc-product-cat-manage-export.php
-│   └── class-studiou-wc-product-cat-manage-import.php
+│   ├── class-studiou-wc-product-cat-manage-import.php
+│   └── class-studiou-wc-product-cat-manage-manipulator.php
 ├── views/
 │   ├── import.php
-│   └── export.php
+│   ├── export.php
+│   └── manipulations.php
 ├── readme.md
 └── studiou-wc-product-cat-manage.php

+ 233 - 0
studiou-wc-product-cat-manage/includes/class-studiou-wc-product-cat-manage-manipulator.php

@@ -0,0 +1,233 @@
+<?php
+/**
+ * Category Manipulator class
+ *
+ * Handles manipulation operations for product categories
+ *
+ * @since      1.0.0
+ * @package    Studiou_WC_Product_Cat_Manage
+ * @subpackage Studiou_WC_Product_Cat_Manage/includes
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+
+class Studiou_WC_Product_Cat_Manage_Manipulator {
+    
+    /**
+     * Database handler
+     *
+     * @var Studiou_WC_Product_Cat_Manage_DB
+     */
+    private $db;
+    
+    /**
+     * Constructor
+     *
+     * @param Studiou_WC_Product_Cat_Manage_DB $db Database handler
+     */
+    public function __construct($db) {
+        $this->db = $db;
+        
+        // Register AJAX handlers
+        add_action('wp_ajax_studiou_wcpcm_move_products', array($this, 'handle_move_products_ajax'));
+    }
+    
+    /**
+     * Handle AJAX move products request
+     */
+    public function handle_move_products_ajax() {
+        // Check nonce
+        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
+            wp_send_json_error(array(
+                'message' => __('Security check failed', 'studiou-wc-product-cat-manage')
+            ));
+        }
+        
+        // Check permissions
+        if (!current_user_can('manage_woocommerce')) {
+            wp_send_json_error(array(
+                'message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')
+            ));
+        }
+        
+        // Validate input
+        if (!isset($_POST['source_category']) || !isset($_POST['target_category'])) {
+            wp_send_json_error(array(
+                'message' => __('Missing required parameters', 'studiou-wc-product-cat-manage')
+            ));
+        }
+        
+        $source_category_id = intval($_POST['source_category']);
+        $target_category_id = intval($_POST['target_category']);
+        
+        // Validate categories
+        if ($source_category_id === $target_category_id) {
+            wp_send_json_error(array(
+                'message' => __('Source and target categories must be different', 'studiou-wc-product-cat-manage')
+            ));
+        }
+        
+        if ($source_category_id <= 0 || $target_category_id <= 0) {
+            wp_send_json_error(array(
+                'message' => __('Invalid category selection', 'studiou-wc-product-cat-manage')
+            ));
+        }
+        
+        try {
+            // Execute move operation
+            $result = $this->move_products_between_categories($source_category_id, $target_category_id);
+            
+            // Return success response
+            wp_send_json_success(array(
+                'message' => $result['message'],
+                'moved_count' => $result['moved_count'],
+                'source_count' => $result['source_count'],
+                'target_count' => $result['target_count']
+            ));
+        } catch (Exception $e) {
+            wp_send_json_error(array(
+                'message' => sprintf(
+                    __('Error during move operation: %s', 'studiou-wc-product-cat-manage'),
+                    $e->getMessage()
+                )
+            ));
+        }
+    }
+    
+    /**
+     * Move all products from source category to target category
+     *
+     * @param int $source_category_id Source category ID
+     * @param int $target_category_id Target category ID
+     * @return array Result with counts and message
+     * @throws Exception On error
+     */
+    public function move_products_between_categories($source_category_id, $target_category_id) {
+        // Get source and target category terms
+        $source_category = get_term($source_category_id, 'product_cat');
+        $target_category = get_term($target_category_id, 'product_cat');
+        
+        if (is_wp_error($source_category) || is_wp_error($target_category)) {
+            throw new Exception(__('Invalid category specified', 'studiou-wc-product-cat-manage'));
+        }
+        
+        // Get all products in source category
+        $product_args = array(
+            'post_type' => 'product',
+            'post_status' => array('publish', 'private', 'draft'),
+            'posts_per_page' => -1,
+            'fields' => 'ids',
+            'tax_query' => array(
+                array(
+                    'taxonomy' => 'product_cat',
+                    'field' => 'term_id',
+                    'terms' => $source_category_id,
+                )
+            )
+        );
+        
+        $products = get_posts($product_args);
+        $moved_count = 0;
+        
+        // Move each product
+        foreach ($products as $product_id) {
+            // Get current categories for the product
+            $current_categories = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
+            
+            if (is_wp_error($current_categories)) {
+                continue;
+            }
+            
+            // Remove source category and add target category
+            $new_categories = array_diff($current_categories, array($source_category_id));
+            $new_categories[] = $target_category_id;
+            
+            // Update product categories
+            $result = wp_set_post_terms($product_id, $new_categories, 'product_cat');
+            
+            if (!is_wp_error($result)) {
+                $moved_count++;
+            }
+        }
+        
+        // Get updated counts
+        $source_count = $this->get_category_product_count($source_category_id);
+        $target_count = $this->get_category_product_count($target_category_id);
+        
+        // Generate result message
+        $message = sprintf(
+            __('Moved %d products from "%s" to "%s". Source category now has %d products, target category now has %d products.', 'studiou-wc-product-cat-manage'),
+            $moved_count,
+            $source_category->name,
+            $target_category->name,
+            $source_count,
+            $target_count
+        );
+        
+        return array(
+            'moved_count' => $moved_count,
+            'source_count' => $source_count,
+            'target_count' => $target_count,
+            'message' => $message
+        );
+    }
+    
+    /**
+     * Get product count for a category
+     *
+     * @param int $category_id Category ID
+     * @return int Product count
+     */
+    private function get_category_product_count($category_id) {
+        $args = array(
+            'post_type' => 'product',
+            'post_status' => array('publish', 'private', 'draft'),
+            'posts_per_page' => -1,
+            'fields' => 'ids',
+            'tax_query' => array(
+                array(
+                    'taxonomy' => 'product_cat',
+                    'field' => 'term_id',
+                    'terms' => $category_id,
+                )
+            )
+        );
+        
+        $products = get_posts($args);
+        return count($products);
+    }
+    
+    /**
+     * Get all categories with product counts for select lists
+     *
+     * @return array Array of categories with ID, name, and product count
+     */
+    public function get_categories_with_counts() {
+        $args = array(
+            'taxonomy' => 'product_cat',
+            'orderby' => 'name',
+            'hide_empty' => false,
+        );
+        
+        $categories = get_terms($args);
+        $categories_with_counts = array();
+        
+        if (!is_wp_error($categories)) {
+            foreach ($categories as $category) {
+                $product_count = $this->get_category_product_count($category->term_id);
+                
+                $categories_with_counts[] = array(
+                    'id' => $category->term_id,
+                    'name' => $category->name,
+                    'count' => $product_count,
+                    'display_name' => sprintf('%s (%d products)', $category->name, $product_count)
+                );
+            }
+        }
+        
+        return $categories_with_counts;
+    }
+}

+ 15 - 5
studiou-wc-product-cat-manage/instructions.txt

@@ -17,7 +17,7 @@ Plugin will have following features:
 	.csv file separator is comma ',' changacter. String values is isolated between quotation mark '"', means if inside quotation mark is comma, comma is not separator. Encoding of import and export file is UTF-8.
 
 
-2. Extends Products menu for items "Import product categories" and "Export product categories". In "Import product categories" there will be button for file selection and second button "Import product categories". When I press this button Import will be processed. After import operation imformation will be shown as follows (sample):
+3. Extends Products menu for items "Import product categories" and "Export product categories". In "Import product categories" there will be button for file selection and second button "Import product categories". When I press this button Import will be processed. After import operation imformation will be shown as follows (sample):
 
 	Imported 5 items:
 		Product category 'Item 0' already exists. Skiped.
@@ -34,11 +34,11 @@ Plugin will have following features:
 		Null reference exception
 	
 
-3. Plugin requires following plugins:
-	WooCommerce minimal version 9.7
+4. Plugin requires following plugins:
+	WooCommerce minimal version 9.8
 	WooCommerce Protected Categories minimal version 2.7
-	
-4. Plugin will have following header:
+
+5. Plugin will have following header:
 
 /**
  * Plugin Name: QDR - Studiou WC Export/Import Product Categories
@@ -58,3 +58,13 @@ Plugin will have following features:
  * Woo: 12345:342928dfsfhsf8429842374wdf4234sfd
  */
 
+6. Implements following features: 
+	- new menu "Products" | "Category manipulations"
+	- In screen there will be several operations (sections) headed by caption (name of operation):
+		- In screen Category manipulations implements operation called "Move products between categories" in header to move all products from selected product category to other selected.
+			- There will be two comboboxes with Category list (with information how many products contains): First will be source product category, second will be target product category.
+			- The will be button "Do" that start operation:
+				- iterate products from source category
+				- product in iteration will me reassigned to target category (and remove from source category)
+				- at the end of operation print out output information how many products was moved and new count of products in source category and new count of products in target ctegory.
+	- all text will be translatable 

+ 44 - 5
studiou-wc-product-cat-manage/readme.md

@@ -2,11 +2,11 @@
 
 **Version: 1.2**
 
-A WordPress plugin that extends WooCommerce to provide batch export and import functionality for product categories.
+A WordPress plugin that extends WooCommerce to provide batch export and import functionality for product categories, along with category manipulation tools.
 
 ## Description
 
-QDR - Studiou WC Export/Import Product Categories is a powerful WordPress plugin for WooCommerce store administrators who need to manage product categories in bulk. This plugin allows you to export your existing product categories to a CSV file, make modifications, and import them back into your store.
+QDR - Studiou WC Export/Import Product Categories is a powerful WordPress plugin for WooCommerce store administrators who need to manage product categories in bulk. This plugin allows you to export your existing product categories to a CSV file, make modifications, and import them back into your store. Additionally, it provides tools for manipulating categories and moving products between categories.
 
 The plugin also supports the WooCommerce Protected Categories extension, allowing you to manage visibility settings and passwords for your product categories.
 
@@ -16,14 +16,15 @@ The plugin also supports the WooCommerce Protected Categories extension, allowin
 - Import product categories from a CSV file
 - Add, update, or delete product categories in batch
 - Support for WooCommerce Protected Categories visibility settings
+- **NEW**: Move products between categories in bulk
 - User-friendly interface integrated with WooCommerce admin
 - Comprehensive validation and error reporting
 
 ## Requirements
 
-- WordPress 6.7.2 or higher
+- WordPress 6.8.1 or higher
 - PHP 8.2 or higher
-- WooCommerce 9.7 or higher
+- WooCommerce 9.8 or higher
 - WooCommerce Protected Categories 2.7 or higher
 
 ## Installation
@@ -64,6 +65,22 @@ The plugin uses CSV files with the following columns:
 3. Click "Import Product Categories"
 4. Review the import results
 
+### Category Manipulations
+
+#### Moving Products Between Categories
+
+1. Go to Products > Category Manipulations
+2. Select the source category (from which products will be moved)
+3. Select the target category (to which products will be moved)
+4. Click "Do" to execute the move operation
+5. Review the results showing how many products were moved
+
+**Important Notes about Moving Products:**
+- Products can be assigned to multiple categories. This operation will remove the product from the source category and add it to the target category.
+- If a product is only assigned to the source category, it will be moved entirely to the target category.
+- If a product is assigned to multiple categories including the source category, it will remain in the other categories and be moved to the target category.
+- The operation processes all products regardless of their status (published, draft, private).
+
 ## Sample CSV
 
 ```
@@ -81,6 +98,14 @@ name,url-name,parent-name,description,display-type,visibility,protected-password
 - `U` - Update an existing product category (identified by name)
 - `D` - Delete an existing product category (identified by name)
 
+## Menu Structure
+
+After activation, the plugin adds the following menu items under **Products** in the WordPress admin:
+
+- Import Product Categories
+- Export Product Categories
+- **Category Manipulations** (NEW)
+
 ## License
 
 GPL v2 or later - https://www.gnu.org/licenses/gpl-2.0.html
@@ -92,4 +117,18 @@ Website: [Quadarax](https://www.quadarax.com)
 
 ## Support
 
-For support, please visit [https://www.quadarax.com/plugins/studiou-wc-product-cat-manage](https://www.quadarax.com/plugins/studiou-wc-product-cat-manage)
+For support, please visit [https://www.quadarax.com/plugins/studiou-wc-product-cat-manage](https://www.quadarax.com/plugins/studiou-wc-product-cat-manage)
+
+## Changelog
+
+### Version 1.2.0
+- Added Category Manipulations functionality
+- New feature: Move products between categories
+- Enhanced admin interface with better user feedback
+- Improved security with proper nonce validation
+- Added comprehensive documentation
+
+### Version 1.1.0
+- Initial release with import/export functionality
+- Support for WooCommerce Protected Categories
+- CSV-based batch operations

+ 28 - 1
studiou-wc-product-cat-manage/studiou-wc-product-cat-manage.php

@@ -48,6 +48,11 @@ class Studiou_WC_Product_Cat_Manage {
      */
     private $db;
     
+    /**
+     * @var Studiou_WC_Product_Cat_Manage_Manipulator
+     */
+    private $manipulator;
+    
     /**
      * Initialize the plugin
      */
@@ -75,6 +80,7 @@ class Studiou_WC_Product_Cat_Manage {
         require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-db.php';
         require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-export.php';
         require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-import.php';
+        require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-manipulator.php';
     }
     
     /**
@@ -84,6 +90,7 @@ class Studiou_WC_Product_Cat_Manage {
         $this->db = new Studiou_WC_Product_Cat_Manage_DB();
         $this->exporter = new Studiou_WC_Product_Cat_Manage_Export($this->db);
         $this->importer = new Studiou_WC_Product_Cat_Manage_Import($this->db);
+        $this->manipulator = new Studiou_WC_Product_Cat_Manage_Manipulator($this->db);
     }
     
     /**
@@ -142,6 +149,15 @@ class Studiou_WC_Product_Cat_Manage {
                 'studiou-export-product-categories',
                 array($this, 'render_export_page')
             );
+            
+            add_submenu_page(
+                'edit.php?post_type=product',
+                __('Category Manipulations', 'studiou-wc-product-cat-manage'),
+                __('Category Manipulations', 'studiou-wc-product-cat-manage'),
+                'manage_woocommerce',
+                'studiou-category-manipulations',
+                array($this, 'render_manipulations_page')
+            );
         }
     }
     
@@ -150,7 +166,8 @@ class Studiou_WC_Product_Cat_Manage {
      */
     public function register_admin_assets($hook) {
         if ('product_page_studiou-import-product-categories' === $hook || 
-            'product_page_studiou-export-product-categories' === $hook) {
+            'product_page_studiou-export-product-categories' === $hook ||
+            'product_page_studiou-category-manipulations' === $hook) {
             
             wp_enqueue_style(
                 'studiou-wcpcm-admin',
@@ -175,6 +192,9 @@ class Studiou_WC_Product_Cat_Manage {
                     'importError' => __('Import error', 'studiou-wc-product-cat-manage'),
                     'exportSuccess' => __('Export successful', 'studiou-wc-product-cat-manage'),
                     'exportError' => __('Export error', 'studiou-wc-product-cat-manage'),
+                    'moveSuccess' => __('Move operation successful', 'studiou-wc-product-cat-manage'),
+                    'moveError' => __('Move operation error', 'studiou-wc-product-cat-manage'),
+                    'selectDifferentCategories' => __('Please select different source and target categories', 'studiou-wc-product-cat-manage'),
                 )
             ));
         }
@@ -193,6 +213,13 @@ class Studiou_WC_Product_Cat_Manage {
     public function render_export_page() {
         include STUDIOU_WCPCM_PLUGIN_DIR . 'views/export.php';
     }
+    
+    /**
+     * Render manipulations page
+     */
+    public function render_manipulations_page() {
+        include STUDIOU_WCPCM_PLUGIN_DIR . 'views/manipulations.php';
+    }
 }
 
 /**

+ 92 - 0
studiou-wc-product-cat-manage/views/manipulations.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * Category Manipulations page view
+ *
+ * @since      1.0.0
+ * @package    Studiou_WC_Product_Cat_Manage
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+
+// Get categories with counts for select boxes
+$manipulator = new Studiou_WC_Product_Cat_Manage_Manipulator(new Studiou_WC_Product_Cat_Manage_DB());
+$categories = $manipulator->get_categories_with_counts();
+?>
+
+<div class="wrap studiou-wcpcm-wrap">
+    <h1><?php echo esc_html__('Category Manipulations', 'studiou-wc-product-cat-manage'); ?></h1>
+    
+    <div class="studiou-wcpcm-notice-area">
+        <!-- Area for displaying notices -->
+    </div>
+    
+    <div class="studiou-wcpcm-card">
+        <h2><?php echo esc_html__('Move products between categories', 'studiou-wc-product-cat-manage'); ?></h2>
+        
+        <p><?php echo esc_html__('This operation allows you to move all products from one category to another. Products will be removed from the source category and added to the target category.', 'studiou-wc-product-cat-manage'); ?></p>
+        
+        <form id="studiou-wcpcm-move-form" method="post">
+            <table class="form-table">
+                <tr>
+                    <th scope="row">
+                        <label for="source_category"><?php echo esc_html__('Source Category', 'studiou-wc-product-cat-manage'); ?></label>
+                    </th>
+                    <td>
+                        <select name="source_category" id="source_category" class="regular-text" required>
+                            <option value=""><?php echo esc_html__('-- Select Source Category --', 'studiou-wc-product-cat-manage'); ?></option>
+                            <?php foreach ($categories as $category): ?>
+                                <option value="<?php echo esc_attr($category['id']); ?>">
+                                    <?php echo esc_html($category['display_name']); ?>
+                                </option>
+                            <?php endforeach; ?>
+                        </select>
+                        <p class="description"><?php echo esc_html__('Select the category from which products will be moved.', 'studiou-wc-product-cat-manage'); ?></p>
+                    </td>
+                </tr>
+                <tr>
+                    <th scope="row">
+                        <label for="target_category"><?php echo esc_html__('Target Category', 'studiou-wc-product-cat-manage'); ?></label>
+                    </th>
+                    <td>
+                        <select name="target_category" id="target_category" class="regular-text" required>
+                            <option value=""><?php echo esc_html__('-- Select Target Category --', 'studiou-wc-product-cat-manage'); ?></option>
+                            <?php foreach ($categories as $category): ?>
+                                <option value="<?php echo esc_attr($category['id']); ?>">
+                                    <?php echo esc_html($category['display_name']); ?>
+                                </option>
+                            <?php endforeach; ?>
+                        </select>
+                        <p class="description"><?php echo esc_html__('Select the category to which products will be moved.', 'studiou-wc-product-cat-manage'); ?></p>
+                    </td>
+                </tr>
+            </table>
+            
+            <div class="submit-buttons">
+                <button type="submit" class="button button-primary" id="studiou-wcpcm-move-button">
+                    <?php echo esc_html__('Do', 'studiou-wc-product-cat-manage'); ?>
+                </button>
+                <span class="spinner"></span>
+            </div>
+        </form>
+        
+        <div class="studiou-wcpcm-move-results" style="display: none;">
+            <h3><?php echo esc_html__('Move Results', 'studiou-wc-product-cat-manage'); ?></h3>
+            <div id="studiou-wcpcm-move-message"></div>
+        </div>
+    </div>
+    
+    <!-- Add more manipulation operations here in the future -->
+    
+    <div class="studiou-wcpcm-card">
+        <h2><?php echo esc_html__('Important Notes', 'studiou-wc-product-cat-manage'); ?></h2>
+        <ul>
+            <li><?php echo esc_html__('Products can be assigned to multiple categories. This operation will remove the product from the source category and add it to the target category.', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><?php echo esc_html__('If a product is only assigned to the source category, it will be moved entirely to the target category.', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><?php echo esc_html__('If a product is assigned to multiple categories including the source category, it will remain in the other categories and be moved to the target category.', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><?php echo esc_html__('The operation processes all products regardless of their status (published, draft, private).', 'studiou-wc-product-cat-manage'); ?></li>
+        </ul>
+    </div>
+</div>