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']) : ?>
'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();