فهرست منبع

Add Remove Not Assigned Media feature, fix physical file deletion — v1.5.2

New feature (ch8): "Remove Not Assigned Media" operation in Category
Manipulations page. Finds all media that is not assigned to any media
category AND not used as featured image, product gallery image, or
attached to any post. Button shows count, with confirmation dialog,
real-time progress bar, batch processing (10 at a time), and
deletion statistics.

Fix (ch7): Media deletion now actually deletes physical files and
attachment posts. After wp_delete_attachment(), verifies via direct
DB query that the post was removed. If not, forces manual cleanup:
deletes term relationships, postmeta, post row, and physical files
(main + thumbnails) from disk.

New AJAX: studiou_wcpcm_remove_unassigned_media,
studiou_wcpcm_remove_unassigned_media_batch

Version bumped to 1.5.2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dalibor Votruba 3 ماه پیش
والد
کامیت
e573694268

+ 12 - 5
studiou-wc-product-cat-manage/assets/css/admin.css

@@ -208,7 +208,9 @@
 .studiou-wcpcm-delete-progress,
 .studiou-wcpcm-delete-results,
 .studiou-wcpcm-clear-media-progress,
-.studiou-wcpcm-clear-media-results {
+.studiou-wcpcm-clear-media-results,
+.studiou-wcpcm-remove-unassigned-media-progress,
+.studiou-wcpcm-remove-unassigned-media-results {
     margin-top: 30px;
     padding: 20px;
     background: #f9f9f9;
@@ -219,7 +221,9 @@
 .studiou-wcpcm-delete-progress h3,
 .studiou-wcpcm-delete-results h3,
 .studiou-wcpcm-clear-media-progress h3,
-.studiou-wcpcm-clear-media-results h3 {
+.studiou-wcpcm-clear-media-results h3,
+.studiou-wcpcm-remove-unassigned-media-progress h3,
+.studiou-wcpcm-remove-unassigned-media-results h3 {
     margin-top: 0;
     margin-bottom: 15px;
 }
@@ -260,18 +264,21 @@
 }
 
 .studiou-wcpcm-delete-results ul,
-.studiou-wcpcm-clear-media-results ul {
+.studiou-wcpcm-clear-media-results ul,
+.studiou-wcpcm-remove-unassigned-media-results ul {
     list-style: none;
     padding-left: 0;
 }
 
 .studiou-wcpcm-delete-results ul li,
-.studiou-wcpcm-clear-media-results ul li {
+.studiou-wcpcm-clear-media-results ul li,
+.studiou-wcpcm-remove-unassigned-media-results ul li {
     padding: 8px 0;
     border-bottom: 1px solid #e0e0e0;
 }
 
 .studiou-wcpcm-delete-results ul li:last-child,
-.studiou-wcpcm-clear-media-results ul li:last-child {
+.studiou-wcpcm-clear-media-results ul li:last-child,
+.studiou-wcpcm-remove-unassigned-media-results ul li:last-child {
     border-bottom: none;
 }

+ 180 - 0
studiou-wc-product-cat-manage/assets/js/admin.js

@@ -34,6 +34,7 @@
         safeInit('Batch description form', '#studiou-wcpcm-batch-description-form', initBatchDescriptionForm);
         safeInit('Delete products form', '#studiou-wcpcm-delete-products-form', initDeleteProductsForm);
         safeInit('Clear media form', '#studiou-wcpcm-clear-media-form', initClearMediaForm);
+        safeInit('Remove unassigned media form', '#studiou-wcpcm-remove-unassigned-media-form', initRemoveUnassignedMediaForm);
         safeInit('Sample download', '#studiou-wcpcm-download-sample', initSampleDownload);
         safeInit('Product import form', '#studiou-wcpcm-product-import-form', initProductImportForm);
         safeInit('Product export form', '#studiou-wcpcm-product-export-form', initProductExportForm);
@@ -833,6 +834,185 @@
         $('#studiou-wcpcm-clear-media-progress-bar').attr('data-progress', percentage + '%');
     }
 
+    /**
+     * Initialize remove unassigned media form
+     */
+    function initRemoveUnassignedMediaForm() {
+        console.log('STUDIOU WC: Initializing remove unassigned media form handlers');
+
+        $('#studiou-wcpcm-remove-unassigned-media-form').on('submit', function(e) {
+            e.preventDefault();
+
+            console.log('STUDIOU WC: Remove unassigned media form submitted');
+
+            if (!confirm(studiouWcpcm.i18n.confirmRemoveUnassigned || 'Are you sure you want to permanently delete ALL unused uncategorized media files? This action cannot be undone!')) {
+                return;
+            }
+
+            var $submitBtn = $(this).find('#studiou-wcpcm-remove-unassigned-media-button');
+            var $spinner = $(this).find('.spinner');
+
+            $submitBtn.prop('disabled', true);
+            $spinner.css('visibility', 'visible');
+
+            $('.studiou-wcpcm-notice-area').empty();
+            $('.studiou-wcpcm-remove-unassigned-media-results').hide();
+            $('.studiou-wcpcm-remove-unassigned-media-progress').show();
+            $('#studiou-wcpcm-remove-unassigned-media-progress-text').text('Initializing...');
+            updateRemoveUnassignedProgressBar(0);
+
+            $.ajax({
+                url: studiouWcpcm.ajaxUrl,
+                type: 'POST',
+                data: {
+                    action: 'studiou_wcpcm_remove_unassigned_media',
+                    nonce: studiouWcpcm.nonce
+                },
+                success: function(response) {
+                    console.log('STUDIOU WC: Remove unassigned media response:', response);
+
+                    if (response.success) {
+                        var mediaIds = response.data.media_ids;
+                        var totalCount = response.data.total_count;
+
+                        if (totalCount === 0) {
+                            showNotice('success', studiouWcpcm.i18n.noUnassignedMedia || 'No unused uncategorized media files found');
+                            $('.studiou-wcpcm-remove-unassigned-media-progress').hide();
+                            $submitBtn.prop('disabled', false);
+                            $spinner.css('visibility', 'hidden');
+                            return;
+                        }
+
+                        processUnassignedMediaBatches(mediaIds, totalCount, $submitBtn, $spinner);
+                    } else {
+                        showNotice('error', response.data.message);
+                        $('.studiou-wcpcm-remove-unassigned-media-progress').hide();
+                        $submitBtn.prop('disabled', false);
+                        $spinner.css('visibility', 'hidden');
+                    }
+                },
+                error: function(xhr, status, error) {
+                    console.error('STUDIOU WC: Remove unassigned media error:', status, error);
+                    showNotice('error', (studiouWcpcm.i18n.removeUnassignedError || 'Error') + ': ' + error);
+                    $('.studiou-wcpcm-remove-unassigned-media-progress').hide();
+                    $submitBtn.prop('disabled', false);
+                    $spinner.css('visibility', 'hidden');
+                }
+            });
+        });
+    }
+
+    /**
+     * Process unassigned media deletion in batches
+     */
+    function processUnassignedMediaBatches(mediaIds, totalCount, $submitBtn, $spinner) {
+        var batchSize = 10;
+        var batches = [];
+        var totalDeleted = 0;
+        var totalFailed = 0;
+        var failedMedia = [];
+
+        for (var i = 0; i < mediaIds.length; i += batchSize) {
+            batches.push(mediaIds.slice(i, i + batchSize));
+        }
+
+        var currentBatch = 0;
+
+        function processBatch() {
+            if (currentBatch >= batches.length) {
+                onUnassignedMediaComplete(totalDeleted, totalFailed, failedMedia, $submitBtn, $spinner);
+                return;
+            }
+
+            var batch = batches[currentBatch];
+            var progress = Math.round((currentBatch / batches.length) * 100);
+
+            $('#studiou-wcpcm-remove-unassigned-media-progress-text').text(
+                'Deleting media files... ' + (currentBatch * batchSize) + ' of ' + totalCount + ' processed'
+            );
+            updateRemoveUnassignedProgressBar(progress);
+
+            $.ajax({
+                url: studiouWcpcm.ajaxUrl,
+                type: 'POST',
+                data: {
+                    action: 'studiou_wcpcm_remove_unassigned_media_batch',
+                    media_ids: batch,
+                    nonce: studiouWcpcm.nonce
+                },
+                success: function(response) {
+                    if (response.success) {
+                        totalDeleted += response.data.deleted_count;
+                        totalFailed += response.data.failed_count;
+                        failedMedia = failedMedia.concat(response.data.failed_media);
+                        currentBatch++;
+                        processBatch();
+                    } else {
+                        showNotice('error', response.data.message);
+                        $('.studiou-wcpcm-remove-unassigned-media-progress').hide();
+                        $submitBtn.prop('disabled', false);
+                        $spinner.css('visibility', 'hidden');
+                    }
+                },
+                error: function(xhr, status, error) {
+                    console.error('STUDIOU WC: Unassigned media batch error:', status, error);
+                    showNotice('error', 'Error deleting batch: ' + error);
+                    $('.studiou-wcpcm-remove-unassigned-media-progress').hide();
+                    $submitBtn.prop('disabled', false);
+                    $spinner.css('visibility', 'hidden');
+                }
+            });
+        }
+
+        processBatch();
+    }
+
+    /**
+     * Handle unassigned media deletion completion
+     */
+    function onUnassignedMediaComplete(totalDeleted, totalFailed, failedMedia, $submitBtn, $spinner) {
+        updateRemoveUnassignedProgressBar(100);
+        $('#studiou-wcpcm-remove-unassigned-media-progress-text').text('Deletion complete!');
+
+        var message = '<p><strong>Deletion Summary:</strong></p>';
+        message += '<ul>';
+        message += '<li>Total media files deleted: ' + totalDeleted + '</li>';
+        if (totalFailed > 0) {
+            message += '<li>Failed to delete: ' + totalFailed + '</li>';
+            if (failedMedia.length > 0) {
+                message += '<li>Failed media IDs: ' + failedMedia.join(', ') + '</li>';
+            }
+        }
+        message += '</ul>';
+
+        $('#studiou-wcpcm-remove-unassigned-media-message').html(message);
+        $('.studiou-wcpcm-remove-unassigned-media-results').show();
+
+        setTimeout(function() {
+            $('.studiou-wcpcm-remove-unassigned-media-progress').fadeOut();
+        }, 2000);
+
+        if (totalDeleted > 0) {
+            showNotice('success', (studiouWcpcm.i18n.removeUnassignedSuccess || 'Successfully deleted') + ' ' + totalDeleted + ' media files');
+            setTimeout(function() {
+                location.reload();
+            }, 3000);
+        } else {
+            showNotice('error', studiouWcpcm.i18n.removeUnassignedError || 'Failed to delete any media files');
+        }
+
+        $submitBtn.prop('disabled', false);
+        $spinner.css('visibility', 'hidden');
+    }
+
+    /**
+     * Update remove unassigned media progress bar
+     */
+    function updateRemoveUnassignedProgressBar(percentage) {
+        $('#studiou-wcpcm-remove-unassigned-media-progress-bar').css('width', percentage + '%');
+        $('#studiou-wcpcm-remove-unassigned-media-progress-bar').attr('data-progress', percentage + '%');
+    }
+
     /**
      * Show notice message
      *

+ 261 - 4
studiou-wc-product-cat-manage/includes/class-studiou-wc-product-cat-manage-manipulator.php

@@ -38,6 +38,8 @@ class Studiou_WC_Product_Cat_Manage_Manipulator {
         add_action('wp_ajax_studiou_wcpcm_delete_products_batch', array($this, 'handle_delete_products_batch_ajax'));
         add_action('wp_ajax_studiou_wcpcm_clear_media', array($this, 'handle_clear_media_ajax'));
         add_action('wp_ajax_studiou_wcpcm_clear_media_batch', array($this, 'handle_clear_media_batch_ajax'));
+        add_action('wp_ajax_studiou_wcpcm_remove_unassigned_media', array($this, 'handle_remove_unassigned_media_ajax'));
+        add_action('wp_ajax_studiou_wcpcm_remove_unassigned_media_batch', array($this, 'handle_remove_unassigned_media_batch_ajax'));
 
         // Add a test AJAX handler to verify AJAX is working
         add_action('wp_ajax_studiou_wcpcm_test', array($this, 'handle_test_ajax'));
@@ -1003,12 +1005,22 @@ class Studiou_WC_Product_Cat_Manage_Manipulator {
      * @return array Result with counts
      */
     private function delete_media_batch($media_ids) {
+        global $wpdb;
+
         $deleted_count = 0;
         $failed_count = 0;
         $failed_media = array();
         $affected_term_ids = array();
         $taxonomy = $this->get_media_category_taxonomy();
 
+        // Ensure required file functions are loaded
+        if (!function_exists('wp_delete_attachment')) {
+            require_once ABSPATH . 'wp-admin/includes/post.php';
+        }
+        if (!function_exists('wp_delete_file')) {
+            require_once ABSPATH . 'wp-includes/functions.php';
+        }
+
         error_log('STUDIOU WC MEDIA: delete_media_batch called with ' . count($media_ids) . ' media IDs');
 
         foreach ($media_ids as $media_id) {
@@ -1030,16 +1042,73 @@ class Studiou_WC_Product_Cat_Manage_Manipulator {
                     }
                 }
 
-                // Force delete attachment and its files from disk
-                $result = wp_delete_attachment($media_id, true);
+                // Get file paths BEFORE deletion
+                $file = get_attached_file($media_id);
+                $meta = wp_get_attachment_metadata($media_id);
+                $upload_dir = wp_get_upload_dir();
 
-                if ($result) {
+                error_log('STUDIOU WC MEDIA: Deleting media ID ' . $media_id . ', file: ' . ($file ?: 'none'));
+
+                // Attempt standard WordPress deletion
+                wp_delete_attachment($media_id, true);
+
+                // Verify the post was actually deleted
+                $still_exists = $wpdb->get_var($wpdb->prepare(
+                    "SELECT ID FROM {$wpdb->posts} WHERE ID = %d",
+                    $media_id
+                ));
+
+                if ($still_exists) {
+                    error_log('STUDIOU WC MEDIA: wp_delete_attachment did not delete post ' . $media_id . ', forcing manual deletion');
+
+                    // Force manual deletion: remove term relationships
+                    $all_taxonomies = get_object_taxonomies('attachment');
+                    if (!empty($all_taxonomies)) {
+                        wp_delete_object_term_relationships($media_id, $all_taxonomies);
+                    }
+
+                    // Remove all post meta
+                    $wpdb->delete($wpdb->postmeta, array('post_id' => $media_id));
+
+                    // Remove the post itself
+                    $wpdb->delete($wpdb->posts, array('ID' => $media_id));
+
+                    // Clean cache
+                    clean_post_cache($media_id);
+                }
+
+                // Delete physical files from disk
+                if ($file && file_exists($file)) {
+                    @unlink($file);
+                    error_log('STUDIOU WC MEDIA: Deleted main file: ' . $file);
+                }
+
+                // Delete thumbnail/intermediate sizes
+                if (!empty($meta['sizes']) && !empty($upload_dir['basedir'])) {
+                    $file_dir = !empty($file) ? trailingslashit(dirname($file)) : '';
+                    foreach ($meta['sizes'] as $size => $sizeinfo) {
+                        if (!empty($sizeinfo['file'])) {
+                            $intermediate_file = $file_dir . $sizeinfo['file'];
+                            if (file_exists($intermediate_file)) {
+                                @unlink($intermediate_file);
+                            }
+                        }
+                    }
+                }
+
+                // Final verification
+                $final_check = $wpdb->get_var($wpdb->prepare(
+                    "SELECT ID FROM {$wpdb->posts} WHERE ID = %d",
+                    $media_id
+                ));
+
+                if (!$final_check) {
                     $deleted_count++;
                     error_log('STUDIOU WC MEDIA: Successfully deleted media file ' . $media_id);
                 } else {
                     $failed_count++;
                     $failed_media[] = $media_id;
-                    error_log('STUDIOU WC MEDIA: Failed to delete media file ' . $media_id);
+                    error_log('STUDIOU WC MEDIA: FAILED to delete media file ' . $media_id . ' even after manual cleanup');
                 }
 
             } catch (Exception $e) {
@@ -1222,4 +1291,192 @@ class Studiou_WC_Product_Cat_Manage_Manipulator {
             ob_end_clean();
         }
     }
+
+    /**
+     * Get count of unassigned unused media files
+     *
+     * @return int Count of media files not in any category and not referenced
+     */
+    public function get_unassigned_unused_media_count() {
+        $ids = $this->get_unassigned_unused_media_ids();
+        return count($ids);
+    }
+
+    /**
+     * Get IDs of media files that are not assigned to any media category
+     * and not used as featured image, product gallery image, or attached to any post.
+     *
+     * @return array Array of attachment IDs
+     */
+    public function get_unassigned_unused_media_ids() {
+        global $wpdb;
+
+        $taxonomy = $this->get_media_category_taxonomy();
+
+        // Base query: all attachments not used as featured image and not attached to a post
+        $sql = "
+            SELECT a.ID
+            FROM {$wpdb->posts} a
+            LEFT JOIN {$wpdb->postmeta} pm_thumb
+                ON a.ID = CAST(pm_thumb.meta_value AS UNSIGNED)
+                AND pm_thumb.meta_key = '_thumbnail_id'
+            WHERE a.post_type = 'attachment'
+            AND a.post_parent = 0
+            AND pm_thumb.meta_id IS NULL
+        ";
+
+        // If media category taxonomy exists, also exclude categorized media
+        if ($taxonomy) {
+            $sql .= $wpdb->prepare("
+                AND a.ID NOT IN (
+                    SELECT tr.object_id
+                    FROM {$wpdb->term_relationships} tr
+                    INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
+                    WHERE tt.taxonomy = %s
+                )
+            ", $taxonomy);
+        }
+
+        $media_ids = $wpdb->get_col($sql);
+
+        if (empty($media_ids)) {
+            return array();
+        }
+
+        $media_ids = array_map('intval', $media_ids);
+
+        // Filter out media used in WooCommerce product galleries (_product_image_gallery)
+        $gallery_meta = $wpdb->get_col(
+            "SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_product_image_gallery' AND meta_value != ''"
+        );
+
+        if (!empty($gallery_meta)) {
+            $gallery_ids = array();
+            foreach ($gallery_meta as $gallery_value) {
+                $ids = array_map('intval', array_filter(explode(',', $gallery_value)));
+                $gallery_ids = array_merge($gallery_ids, $ids);
+            }
+            $gallery_ids = array_unique($gallery_ids);
+
+            if (!empty($gallery_ids)) {
+                $media_ids = array_diff($media_ids, $gallery_ids);
+            }
+        }
+
+        error_log('STUDIOU WC MEDIA: Found ' . count($media_ids) . ' unassigned unused media files');
+
+        return array_values($media_ids);
+    }
+
+    /**
+     * Handle AJAX remove unassigned media request (get IDs)
+     */
+    public function handle_remove_unassigned_media_ajax() {
+        while (ob_get_level()) {
+            ob_end_clean();
+        }
+
+        ob_start();
+
+        $original_error_reporting = error_reporting();
+        error_reporting(0);
+
+        error_log('STUDIOU WC MEDIA: Remove unassigned media AJAX handler called');
+
+        try {
+            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')));
+                return;
+            }
+
+            if (!current_user_can('manage_woocommerce')) {
+                wp_send_json_error(array('message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')));
+                return;
+            }
+
+            $media_ids = $this->get_unassigned_unused_media_ids();
+
+            error_log('STUDIOU WC MEDIA: Found ' . count($media_ids) . ' unassigned unused media files to delete');
+
+            ob_clean();
+
+            wp_send_json_success(array(
+                'media_ids' => $media_ids,
+                'total_count' => count($media_ids),
+                'message' => sprintf(
+                    __('Found %d unused uncategorized media files to delete', 'studiou-wc-product-cat-manage'),
+                    count($media_ids)
+                )
+            ));
+
+        } catch (Exception $e) {
+            error_log('STUDIOU WC MEDIA: Remove unassigned media failed - ' . $e->getMessage());
+            ob_clean();
+            wp_send_json_error(array(
+                'message' => sprintf(__('Error: %s', 'studiou-wc-product-cat-manage'), $e->getMessage())
+            ));
+        } finally {
+            error_reporting($original_error_reporting);
+            ob_end_clean();
+        }
+    }
+
+    /**
+     * Handle AJAX remove unassigned media batch request
+     */
+    public function handle_remove_unassigned_media_batch_ajax() {
+        while (ob_get_level()) {
+            ob_end_clean();
+        }
+
+        ob_start();
+
+        $original_error_reporting = error_reporting();
+        error_reporting(0);
+
+        error_log('STUDIOU WC MEDIA: Remove unassigned media batch AJAX handler called');
+
+        try {
+            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')));
+                return;
+            }
+
+            if (!current_user_can('manage_woocommerce')) {
+                wp_send_json_error(array('message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')));
+                return;
+            }
+
+            if (!isset($_POST['media_ids'])) {
+                wp_send_json_error(array('message' => __('Missing required parameters', 'studiou-wc-product-cat-manage')));
+                return;
+            }
+
+            $media_ids = array_map('intval', $_POST['media_ids']);
+
+            error_log('STUDIOU WC MEDIA: Processing batch of ' . count($media_ids) . ' unassigned media for deletion');
+
+            $result = $this->delete_media_batch($media_ids);
+
+            error_log('STUDIOU WC MEDIA: Unassigned batch completed - Deleted: ' . $result['deleted_count'] . ', Failed: ' . $result['failed_count']);
+
+            ob_clean();
+
+            wp_send_json_success(array(
+                'deleted_count' => $result['deleted_count'],
+                'failed_count' => $result['failed_count'],
+                'failed_media' => $result['failed_media']
+            ));
+
+        } catch (Exception $e) {
+            error_log('STUDIOU WC MEDIA: Unassigned media batch failed - ' . $e->getMessage());
+            ob_clean();
+            wp_send_json_error(array(
+                'message' => sprintf(__('Error: %s', 'studiou-wc-product-cat-manage'), $e->getMessage())
+            ));
+        } finally {
+            error_reporting($original_error_reporting);
+            ob_end_clean();
+        }
+    }
 }

+ 11 - 0
studiou-wc-product-cat-manage/instructions.txt

@@ -85,3 +85,14 @@ Plugin will have following features:
 		- Comprehensive deletion statistics and error reporting
 		- Enhanced warning messages for destructive operations
 		- Improved user interface with confirmation dialogs
+
+8. Implements following features: 
+	- in existing menu "Products" | "Category manipulations"
+		- In screen Category manipulations implements operation called "Remove Not Assigned Media" in header to delete (permanently) all uncategorized media (that is not used or referenced) files.		
+		- The will be button "Delete unused uncategorized media" that will follows number of medias. Button start operation
+		- Real-time progress bar for deletion operations
+		- Batch processing for large-scale deletions (processes 10 medias at a time)		
+		- Comprehensive deletion statistics and error reporting
+		- Enhanced warning messages for destructive operations
+		- Improved user interface with confirmation dialogs
+

+ 6 - 2
studiou-wc-product-cat-manage/studiou-wc-product-cat-manage.php

@@ -3,7 +3,7 @@
  * Plugin Name: QDR - Studiou WC Export/Import Product Categories
  * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-product-cat-manage
  * Description: Allows batch import and export WooCommerce product categories and products
- * Version: 1.5.1
+ * Version: 1.5.2
  * Requires at least: 6.8.1
  * Requires PHP: 8.2
  * Author: Dalibor Votruba
@@ -23,7 +23,7 @@ if (!defined('WPINC')) {
 }
 
 // Define plugin constants
-if (!defined('STUDIOU_WCPCM_VERSION')) define('STUDIOU_WCPCM_VERSION', '1.5.1');
+if (!defined('STUDIOU_WCPCM_VERSION')) define('STUDIOU_WCPCM_VERSION', '1.5.2');
 if (!defined('STUDIOU_WCPCM_PLUGIN_DIR')) define('STUDIOU_WCPCM_PLUGIN_DIR', plugin_dir_path(__FILE__));
 if (!defined('STUDIOU_WCPCM_PLUGIN_URL')) define('STUDIOU_WCPCM_PLUGIN_URL', plugin_dir_url(__FILE__));
 if (!defined('STUDIOU_WCPCM_PLUGIN_BASENAME')) define('STUDIOU_WCPCM_PLUGIN_BASENAME', plugin_basename(__FILE__));
@@ -277,6 +277,10 @@ class Studiou_WC_Product_Cat_Manage {
                     'clearMediaSuccess' => __('Clear media operation successful', 'studiou-wc-product-cat-manage'),
                     'clearMediaError' => __('Clear media operation error', 'studiou-wc-product-cat-manage'),
                     'noMediaFound' => __('No media files found in selected categories', 'studiou-wc-product-cat-manage'),
+                    'confirmRemoveUnassigned' => __('Are you sure you want to permanently delete ALL unused uncategorized media files? This action cannot be undone!', 'studiou-wc-product-cat-manage'),
+                    'removeUnassignedSuccess' => __('Remove unassigned media operation successful', 'studiou-wc-product-cat-manage'),
+                    'removeUnassignedError' => __('Remove unassigned media operation error', 'studiou-wc-product-cat-manage'),
+                    'noUnassignedMedia' => __('No unused uncategorized media files found', 'studiou-wc-product-cat-manage'),
                 )
             ));
             

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

@@ -15,6 +15,7 @@ if (!defined('WPINC')) {
 $manipulator = new Studiou_WC_Product_Cat_Manage_Manipulator(new Studiou_WC_Product_Cat_Manage_DB());
 $categories = $manipulator->get_categories_with_counts();
 $media_categories = $manipulator->get_media_categories_with_counts();
+$unassigned_media_count = $manipulator->get_unassigned_unused_media_count();
 ?>
 
 <div class="wrap studiou-wcpcm-wrap">
@@ -267,6 +268,39 @@ $media_categories = $manipulator->get_media_categories_with_counts();
         <?php endif; ?>
     </div>
 
+    <!-- Remove Not Assigned Media Operation -->
+    <div class="studiou-wcpcm-card">
+        <h2><?php echo esc_html__('Remove Not Assigned Media', 'studiou-wc-product-cat-manage'); ?></h2>
+
+        <p><?php echo esc_html__('This operation permanently deletes all media files that are not assigned to any media category and are not used as featured image, product gallery image, or attached to any post.', 'studiou-wc-product-cat-manage'); ?></p>
+
+        <form id="studiou-wcpcm-remove-unassigned-media-form" method="post" onsubmit="return false;">
+            <div class="studiou-wcpcm-warning-message">
+                <p><strong><?php echo esc_html__('WARNING: This operation will permanently delete all unused uncategorized media files from the server. This action cannot be undone!', 'studiou-wc-product-cat-manage'); ?></strong></p>
+            </div>
+
+            <div class="submit-buttons">
+                <button type="submit" class="button button-primary button-danger" id="studiou-wcpcm-remove-unassigned-media-button">
+                    <?php echo esc_html(sprintf(__('Delete unused uncategorized media (%d)', 'studiou-wc-product-cat-manage'), $unassigned_media_count)); ?>
+                </button>
+                <span class="spinner"></span>
+            </div>
+        </form>
+
+        <div class="studiou-wcpcm-remove-unassigned-media-progress" style="display: none;">
+            <h3><?php echo esc_html__('Deletion Progress', 'studiou-wc-product-cat-manage'); ?></h3>
+            <div class="studiou-wcpcm-progress-bar-container">
+                <div class="studiou-wcpcm-progress-bar" id="studiou-wcpcm-remove-unassigned-media-progress-bar"></div>
+            </div>
+            <div class="studiou-wcpcm-progress-text" id="studiou-wcpcm-remove-unassigned-media-progress-text"></div>
+        </div>
+
+        <div class="studiou-wcpcm-remove-unassigned-media-results" style="display: none;">
+            <h3><?php echo esc_html__('Deletion Results', 'studiou-wc-product-cat-manage'); ?></h3>
+            <div id="studiou-wcpcm-remove-unassigned-media-message"></div>
+        </div>
+    </div>
+
     <!-- Important Notes -->
     <div class="studiou-wcpcm-card">
         <h2><?php echo esc_html__('Important Notes', 'studiou-wc-product-cat-manage'); ?></h2>
@@ -278,6 +312,7 @@ $media_categories = $manipulator->get_media_categories_with_counts();
             <li><?php echo esc_html__('The batch description operation will overwrite the existing descriptions for all selected categories.', 'studiou-wc-product-cat-manage'); ?></li>
             <li><strong><?php echo esc_html__('The delete products operation will permanently delete all products and variants from the selected categories. This action cannot be undone!', 'studiou-wc-product-cat-manage'); ?></strong></li>
             <li><strong><?php echo esc_html__('The clear media categories operation will permanently delete all media files from the selected media categories and remove them from the server. This action cannot be undone!', 'studiou-wc-product-cat-manage'); ?></strong></li>
+            <li><strong><?php echo esc_html__('The remove not assigned media operation will permanently delete all media files that are not categorized and not used anywhere. This action cannot be undone!', 'studiou-wc-product-cat-manage'); ?></strong></li>
         </ul>
     </div>
 </div>