array($this, 'sanitize_settings') )); register_setting('abovewp_bulk_attribute_change_progress', 'abovewp_bulk_attribute_change_progress', array( 'sanitize_callback' => array($this, 'sanitize_progress') )); } /** * Sanitize settings */ public function sanitize_settings($input) { $sanitized = array(); if (isset($input['attribute_name'])) { $sanitized['attribute_name'] = sanitize_text_field($input['attribute_name']); } if (isset($input['source_terms'])) { $sanitized['source_terms'] = sanitize_text_field($input['source_terms']); } if (isset($input['target_term'])) { $sanitized['target_term'] = sanitize_text_field($input['target_term']); } if (isset($input['batch_size'])) { $sanitized['batch_size'] = min(max(intval($input['batch_size']), 1), 100); } return $sanitized; } /** * Sanitize progress */ public function sanitize_progress($input) { $sanitized = array(); if (isset($input['total'])) { $sanitized['total'] = intval($input['total']); } if (isset($input['processed'])) { $sanitized['processed'] = intval($input['processed']); } if (isset($input['updated'])) { $sanitized['updated'] = intval($input['updated']); } if (isset($input['failed'])) { $sanitized['failed'] = intval($input['failed']); } if (isset($input['last_offset'])) { $sanitized['last_offset'] = intval($input['last_offset']); } return $sanitized; } /** * Add admin menu */ public function add_admin_menu() { // Add our plugin as a submenu item add_submenu_page( 'abovewp', __('AboveWP Bulk Attribute Change', 'abovewp-bulk-attribute-change'), __('Bulk Attribute Change', 'abovewp-bulk-attribute-change'), 'manage_options', 'abovewp-bulk-attribute-change', array($this, 'admin_page') ); } /** * Display plugin card on AboveWP dashboard */ public function display_plugin_card() { ?>

'pa_proizvoditel', 'source_terms' => '374,373', 'target_term' => '371', 'batch_size' => 5 )); // Get progress data $progress = get_option('abovewp_bulk_attribute_change_progress', array( 'total' => 0, 'processed' => 0, 'updated' => 0, 'failed' => 0, 'last_offset' => 0 )); // Get all product attributes $attribute_taxonomies = function_exists('wc_get_attribute_taxonomies') ? wc_get_attribute_taxonomies() : array(); ?>

' . esc_html__('Click here', 'abovewp-bulk-attribute-change') . '' ); ?>

0 && $progress['processed'] < $progress['total']) : ?>

0%

'You do not have permission to perform this action.')); } $operation = isset($_POST['operation']) ? sanitize_text_field(wp_unslash($_POST['operation'])) : ''; if ($operation === 'save_progress') { // Save progress $progress = array( 'total' => isset($_POST['total']) ? intval($_POST['total']) : 0, 'processed' => isset($_POST['processed']) ? intval($_POST['processed']) : 0, 'updated' => isset($_POST['updated']) ? intval($_POST['updated']) : 0, 'failed' => isset($_POST['failed']) ? intval($_POST['failed']) : 0, 'last_offset' => isset($_POST['offset']) ? intval($_POST['offset']) : 0 ); update_option('abovewp_bulk_attribute_change_progress', $progress); wp_send_json_success(); return; } elseif ($operation === 'reset_progress') { // Reset progress $progress = array( 'total' => 0, 'processed' => 0, 'updated' => 0, 'failed' => 0, 'last_offset' => 0 ); update_option('abovewp_bulk_attribute_change_progress', $progress); wp_send_json_success(); return; } // Get attribute name from request or use default $attribute_name = isset($_POST['attribute_name']) ? sanitize_text_field(wp_unslash($_POST['attribute_name'])) : 'pa_proizvoditel'; // Get source terms from request $source_terms_string = isset($_POST['source_terms']) ? sanitize_text_field(wp_unslash($_POST['source_terms'])) : '374,373'; $source_terms = array_map('intval', explode(',', $source_terms_string)); // Get target term from request $target_term = isset($_POST['target_term']) ? intval($_POST['target_term']) : 371; // Validate the attribute exists if (!taxonomy_exists($attribute_name)) { wp_send_json_error(array('message' => 'The specified attribute does not exist.')); } if ($operation === 'count') { // Get term taxonomy IDs for the source terms $term_taxonomy_ids = array(); foreach ($source_terms as $term_id) { $term = get_term($term_id, $attribute_name); if ($term && !is_wp_error($term)) { $term_taxonomy_ids[] = $term->term_taxonomy_id; } } if (empty($term_taxonomy_ids)) { wp_send_json_success(array('count' => 0)); return; } // Count products with these terms using WordPress built-in functions $all_product_ids = get_posts(array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', 'tax_query' => array( array( 'taxonomy' => $attribute_name, 'field' => 'term_id', 'terms' => $source_terms, 'operator' => 'IN' ) ), 'meta_query' => array(), // Ensure we don't inherit any meta queries 'suppress_filters' => true, // Improve performance )); $count = count($all_product_ids); // Store all product IDs in a transient for accurate tracking set_transient('abovewp_bulk_attribute_change_all_products', $all_product_ids, 3600); // 1 hour expiration wp_send_json_success(array( 'count' => $count, 'all_product_ids' => $all_product_ids )); } elseif ($operation === 'process') { $batch = isset($_POST['batch']) ? intval($_POST['batch']) : 5; $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0; // Limit batch size but allow up to 100 as requested $batch = min(max($batch, 1), 100); // Get all product IDs from transient $all_product_ids = get_transient('abovewp_bulk_attribute_change_all_products'); // If transient expired or doesn't exist, get product IDs again if (!$all_product_ids) { // Get all product IDs using WordPress built-in functions $all_product_ids = get_posts(array( 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', 'tax_query' => array( array( 'taxonomy' => $attribute_name, 'field' => 'term_id', 'terms' => $source_terms, 'operator' => 'IN' ) ), 'meta_query' => array(), // Ensure we don't inherit any meta queries 'suppress_filters' => true, // Improve performance )); // Store all product IDs in a transient set_transient('abovewp_bulk_attribute_change_all_products', $all_product_ids, 3600); // 1 hour expiration } // Get total count $total_count = count($all_product_ids); // Get product IDs for this batch $batch_product_ids = array_slice($all_product_ids, $offset, $batch); $products_in_batch = count($batch_product_ids); // Calculate remaining products accurately $remaining = max(0, $total_count - ($offset + $products_in_batch)); $processed = 0; $updated = 0; $failed = 0; $updated_products = array(); $failed_products = array(); foreach ($batch_product_ids as $product_id) { $product = wc_get_product($product_id); if (!$product) { continue; } $processed++; try { // Get current terms for this product $current_terms = wp_get_object_terms($product_id, $attribute_name, array('fields' => 'ids')); if (is_wp_error($current_terms)) { throw new Exception($current_terms->get_error_message()); } // Ensure all term IDs are integers for consistent comparison $current_terms = array_map('intval', $current_terms); // Check if product actually has any of the source terms $has_source_terms = !empty(array_intersect($current_terms, $source_terms)); if (!$has_source_terms) { continue; // Skip this product if it doesn't have the source terms } // Remove source terms, keep others $new_terms = array_diff($current_terms, $source_terms); // Add target term if not already present if (!in_array($target_term, $new_terms)) { $new_terms[] = $target_term; } // Ensure new_terms array is properly indexed (remove any gaps) $new_terms = array_values($new_terms); // Only update if there are actual changes if ($current_terms !== $new_terms) { // Update product attribute terms $result = wp_set_object_terms($product_id, $new_terms, $attribute_name); if (is_wp_error($result)) { throw new Exception($result->get_error_message()); } // Clear product cache wc_delete_product_transients($product_id); $updated++; $updated_products[] = array( 'id' => $product_id, 'name' => get_the_title($product_id), ); } } catch (Exception $e) { $failed++; $failed_products[] = array( 'id' => $product_id, 'name' => get_the_title($product_id), 'error' => $e->getMessage() ); } } wp_send_json_success(array( 'processed' => $processed, 'updated' => $updated, 'failed' => $failed, 'updated_products' => $updated_products, 'failed_products' => $failed_products, 'total_remaining' => intval($remaining), 'total_count' => $total_count, 'current_offset' => $offset, 'next_offset' => $offset + $products_in_batch, 'percent_complete' => $total_count > 0 ? round((($offset + $products_in_batch) / $total_count) * 100) : 100 )); } else { wp_send_json_error(array('message' => 'Invalid operation.')); } } /** * Add favicon to admin pages */ public function add_favicon() { // Only add favicon on our plugin's page $screen = get_current_screen(); if ($screen && $screen->id === 'abovewp_page_abovewp-bulk-attribute-change') { $favicon_svg = ''; echo ''; } } /** * Remove WordPress admin notices on plugin pages */ public function remove_admin_notices_on_plugin_pages() { $screen = get_current_screen(); // Only remove notices on our plugin pages if (isset($screen->id) && ( strpos($screen->id, 'abovewp-bulk-attribute-change') !== false || strpos($screen->id, 'toplevel_page_abovewp') !== false )) { // Remove all admin notices remove_all_actions('admin_notices'); remove_all_actions('all_admin_notices'); } } /** * Enqueue admin styles and scripts */ public function enqueue_admin_styles($hook) { // Only load on our plugin's admin page if (strpos($hook, 'abovewp-bulk-attribute-change') !== false) { // Enqueue Inter font wp_enqueue_style( 'abovewp-font-inter', 'https://fonts.bunny.net/css?family=inter:400,500,600,700', array(), null ); // Enqueue CSS wp_enqueue_style( 'abovewp-admin-default', ABOVEWP_BAC_PLUGIN_URL . 'assets/css/admin-page-default.css', array('abovewp-font-inter'), ABOVEWP_BAC_VERSION ); // Add inline CSS to hide admin notices $hide_notices_css = ' .notice, .updated, .update-nag, .error, .warning, .info { display: none !important; } '; wp_add_inline_style('abovewp-admin-default', $hide_notices_css); // Enqueue JavaScript wp_enqueue_script('jquery'); wp_enqueue_script( 'abovewp-bulk-attribute-change-admin', ABOVEWP_BAC_PLUGIN_URL . 'assets/js/admin-bulk-attribute-change.js', array('jquery'), ABOVEWP_BAC_VERSION, true ); // Get saved options and progress for localization $options = get_option('abovewp_bulk_attribute_change_options', array( 'attribute_name' => 'pa_proizvoditel', 'source_terms' => '374,373', 'target_term' => '371', 'batch_size' => 5 )); $progress = get_option('abovewp_bulk_attribute_change_progress', array( 'total' => 0, 'processed' => 0, 'updated' => 0, 'failed' => 0, 'last_offset' => 0 )); // Localize script to pass PHP variables to JavaScript wp_localize_script( 'abovewp-bulk-attribute-change-admin', 'bulkAttributeChange', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('abovewp_bulk_attribute_change'), 'options' => $options, 'progress' => $progress ) ); } } } // Initialize the plugin new AboveWP_Bulk_Attribute_Change();