|
|
@@ -40,6 +40,10 @@ class Studiou_WC_Product_Cat_Manage_Manipulator {
|
|
|
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_action('wp_ajax_studiou_wcpcm_upvp_get_attributes', array($this, 'handle_upvp_get_attributes_ajax'));
|
|
|
+ add_action('wp_ajax_studiou_wcpcm_upvp_get_attribute_values', array($this, 'handle_upvp_get_attribute_values_ajax'));
|
|
|
+ add_action('wp_ajax_studiou_wcpcm_upvp_count', array($this, 'handle_upvp_count_ajax'));
|
|
|
+ add_action('wp_ajax_studiou_wcpcm_upvp_apply', array($this, 'handle_upvp_apply_ajax'));
|
|
|
|
|
|
// Add a test AJAX handler to verify AJAX is working
|
|
|
add_action('wp_ajax_studiou_wcpcm_test', array($this, 'handle_test_ajax'));
|
|
|
@@ -1479,4 +1483,605 @@ class Studiou_WC_Product_Cat_Manage_Manipulator {
|
|
|
ob_end_clean();
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // ========================================================================
|
|
|
+ // Update Product Variant Prices (UPVP) — v1.5.3
|
|
|
+ // Bulk-update regular_price on product_variation records by category +
|
|
|
+ // attribute filter. Touches only product records; never order items, carts,
|
|
|
+ // or historical order data.
|
|
|
+ // ========================================================================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get list of attribute "heads" usable for variations.
|
|
|
+ *
|
|
|
+ * Source 1: every wc_get_attribute_taxonomies() entry (global attributes)
|
|
|
+ * — key = 'pa_<slug>', is_taxonomy = true.
|
|
|
+ * Source 2: distinct meta_key starting with 'attribute_' on product_variation
|
|
|
+ * rows that are NOT global taxonomies (custom product-level
|
|
|
+ * attributes flagged for variations) — key = '<slug>',
|
|
|
+ * is_taxonomy = false.
|
|
|
+ *
|
|
|
+ * @return array [ ['key' => string, 'label' => string, 'is_taxonomy' => bool], ... ]
|
|
|
+ */
|
|
|
+ public function get_variation_attribute_heads() {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ $heads = array();
|
|
|
+ $seen = array();
|
|
|
+
|
|
|
+ // Source 1: global taxonomies.
|
|
|
+ $taxonomies = function_exists('wc_get_attribute_taxonomies') ? wc_get_attribute_taxonomies() : array();
|
|
|
+ if (!empty($taxonomies)) {
|
|
|
+ foreach ($taxonomies as $tax) {
|
|
|
+ $key = 'pa_' . $tax->attribute_name;
|
|
|
+ if (isset($seen[$key])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $seen[$key] = true;
|
|
|
+ $heads[] = array(
|
|
|
+ 'key' => $key,
|
|
|
+ 'label' => !empty($tax->attribute_label) ? $tax->attribute_label : $tax->attribute_name,
|
|
|
+ 'is_taxonomy' => true,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Source 2: existing variation postmeta (attribute_*).
|
|
|
+ $sql = "
|
|
|
+ SELECT DISTINCT pm.meta_key
|
|
|
+ FROM {$wpdb->postmeta} pm
|
|
|
+ INNER JOIN {$wpdb->posts} v ON v.ID = pm.post_id
|
|
|
+ WHERE v.post_type = 'product_variation'
|
|
|
+ AND v.post_status IN ('publish','private')
|
|
|
+ AND pm.meta_key LIKE %s
|
|
|
+ ";
|
|
|
+ $rows = $wpdb->get_col($wpdb->prepare($sql, $wpdb->esc_like('attribute_') . '%'));
|
|
|
+ if (!empty($rows)) {
|
|
|
+ foreach ($rows as $meta_key) {
|
|
|
+ $slug = substr($meta_key, strlen('attribute_'));
|
|
|
+ if ($slug === '' || isset($seen[$slug])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $seen[$slug] = true;
|
|
|
+ $heads[] = array(
|
|
|
+ 'key' => $slug,
|
|
|
+ 'label' => wc_attribute_label($slug),
|
|
|
+ 'is_taxonomy' => (strpos($slug, 'pa_') === 0) || taxonomy_exists($slug),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Source 3: variable parent products' _product_attributes postmeta.
|
|
|
+ // Catches attributes flagged "Used for variations" on the product's
|
|
|
+ // Attributes tab, even when (a) the attribute is a custom non-global
|
|
|
+ // entry, or (b) no variations have been generated yet.
|
|
|
+ $sql_parents = "
|
|
|
+ SELECT pm.meta_value
|
|
|
+ FROM {$wpdb->postmeta} pm
|
|
|
+ INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
|
|
+ WHERE p.post_type = 'product'
|
|
|
+ AND p.post_status IN ('publish','private','draft')
|
|
|
+ AND pm.meta_key = '_product_attributes'
|
|
|
+ ";
|
|
|
+ $serialized_rows = $wpdb->get_col($sql_parents);
|
|
|
+ if (!empty($serialized_rows)) {
|
|
|
+ foreach ($serialized_rows as $serialized) {
|
|
|
+ $attrs = maybe_unserialize($serialized);
|
|
|
+ if (!is_array($attrs)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ foreach ($attrs as $attr_key => $attr_data) {
|
|
|
+ if (!is_array($attr_data) || empty($attr_data['is_variation'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $is_taxonomy = !empty($attr_data['is_taxonomy']);
|
|
|
+ $name = isset($attr_data['name']) ? $attr_data['name'] : $attr_key;
|
|
|
+ $key = $is_taxonomy ? $name : sanitize_title($name);
|
|
|
+ if ($key === '' || isset($seen[$key])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $seen[$key] = true;
|
|
|
+ $heads[] = array(
|
|
|
+ 'key' => $key,
|
|
|
+ 'label' => $is_taxonomy ? wc_attribute_label($key) : $name,
|
|
|
+ 'is_taxonomy' => $is_taxonomy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sort alphabetically by label.
|
|
|
+ usort($heads, function($a, $b) {
|
|
|
+ return strcasecmp($a['label'], $b['label']);
|
|
|
+ });
|
|
|
+
|
|
|
+ return $heads;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get distinct values for the given attribute key, scoped to existing
|
|
|
+ * variations (and optionally to a set of parent product categories).
|
|
|
+ *
|
|
|
+ * For taxonomy attributes (pa_*) the meta_value is the term slug; we
|
|
|
+ * resolve labels via get_term_by('slug', ..., $taxonomy).
|
|
|
+ *
|
|
|
+ * @param string $attribute_key E.g. 'pa_color' or 'size'.
|
|
|
+ * @param int[] $category_ids Optional list of parent product_cat term IDs.
|
|
|
+ * @return array [ ['value' => string, 'label' => string], ... ]
|
|
|
+ */
|
|
|
+ public function get_variation_attribute_values($attribute_key, $category_ids = array()) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ $attribute_key = sanitize_text_field($attribute_key);
|
|
|
+ if ($attribute_key === '') {
|
|
|
+ return array();
|
|
|
+ }
|
|
|
+
|
|
|
+ $meta_key = 'attribute_' . wc_sanitize_taxonomy_name($attribute_key);
|
|
|
+
|
|
|
+ $params = array($meta_key);
|
|
|
+
|
|
|
+ $cat_join = '';
|
|
|
+ $cat_where = '';
|
|
|
+ if (!empty($category_ids)) {
|
|
|
+ $cat_ids = array_map('intval', $category_ids);
|
|
|
+ $cat_ids = array_filter($cat_ids, function($v) { return $v > 0; });
|
|
|
+ if (!empty($cat_ids)) {
|
|
|
+ $placeholders = implode(',', array_fill(0, count($cat_ids), '%d'));
|
|
|
+ $cat_join = "
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
|
|
|
+ ";
|
|
|
+ $cat_where = "
|
|
|
+ AND tt.taxonomy = 'product_cat'
|
|
|
+ AND tt.term_id IN ($placeholders)
|
|
|
+ ";
|
|
|
+ $params = array_merge($params, $cat_ids);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $sql = "
|
|
|
+ SELECT DISTINCT pm.meta_value
|
|
|
+ FROM {$wpdb->postmeta} pm
|
|
|
+ INNER JOIN {$wpdb->posts} v ON v.ID = pm.post_id
|
|
|
+ AND v.post_type = 'product_variation'
|
|
|
+ AND v.post_status IN ('publish','private')
|
|
|
+ INNER JOIN {$wpdb->posts} p ON p.ID = v.post_parent
|
|
|
+ AND p.post_type = 'product'
|
|
|
+ $cat_join
|
|
|
+ WHERE pm.meta_key = %s
|
|
|
+ AND pm.meta_value <> ''
|
|
|
+ $cat_where
|
|
|
+ ";
|
|
|
+
|
|
|
+ $values = $wpdb->get_col($wpdb->prepare($sql, ...$params));
|
|
|
+
|
|
|
+ $is_taxonomy = (strpos($attribute_key, 'pa_') === 0) || taxonomy_exists($attribute_key);
|
|
|
+
|
|
|
+ // Fallback: if SQL returned no values but the attribute exists on a
|
|
|
+ // variable parent's _product_attributes (e.g. variations not generated
|
|
|
+ // yet, or custom attribute), parse pipe-separated values from there.
|
|
|
+ if (empty($values)) {
|
|
|
+ $sanitized_key = $is_taxonomy
|
|
|
+ ? (strpos($attribute_key, 'pa_') === 0 ? $attribute_key : 'pa_' . $attribute_key)
|
|
|
+ : sanitize_title($attribute_key);
|
|
|
+
|
|
|
+ $sql_parents = "
|
|
|
+ SELECT pm.meta_value
|
|
|
+ FROM {$wpdb->postmeta} pm
|
|
|
+ INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
|
|
+ WHERE p.post_type = 'product'
|
|
|
+ AND p.post_status IN ('publish','private','draft')
|
|
|
+ AND pm.meta_key = '_product_attributes'
|
|
|
+ ";
|
|
|
+ $rows = $wpdb->get_col($sql_parents);
|
|
|
+ $values = array();
|
|
|
+ if (!empty($rows)) {
|
|
|
+ foreach ($rows as $serialized) {
|
|
|
+ $attrs = maybe_unserialize($serialized);
|
|
|
+ if (!is_array($attrs) || !isset($attrs[$sanitized_key])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $entry = $attrs[$sanitized_key];
|
|
|
+ if (empty($entry['is_variation'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ($is_taxonomy) {
|
|
|
+ // Taxonomy custom values are stored as term IDs in 'value'.
|
|
|
+ $term_ids = !empty($entry['value']) ? wp_parse_id_list($entry['value']) : array();
|
|
|
+ if (empty($term_ids)) {
|
|
|
+ // Or the parent uses all terms — pull them from the taxonomy.
|
|
|
+ $tax_name = $sanitized_key;
|
|
|
+ $terms = get_terms(array('taxonomy' => $tax_name, 'hide_empty' => false));
|
|
|
+ if (!is_wp_error($terms)) {
|
|
|
+ foreach ($terms as $t) {
|
|
|
+ $values[] = $t->slug;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ foreach ($term_ids as $tid) {
|
|
|
+ $term = get_term($tid, $sanitized_key);
|
|
|
+ if ($term && !is_wp_error($term)) {
|
|
|
+ $values[] = $term->slug;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $raw = isset($entry['value']) ? (string) $entry['value'] : '';
|
|
|
+ if ($raw !== '') {
|
|
|
+ $parts = array_map('trim', explode('|', $raw));
|
|
|
+ foreach ($parts as $p) {
|
|
|
+ if ($p !== '') {
|
|
|
+ $values[] = $p;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $values = array_values(array_unique($values));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($values)) {
|
|
|
+ return array();
|
|
|
+ }
|
|
|
+
|
|
|
+ $result = array();
|
|
|
+ foreach ($values as $value) {
|
|
|
+ $label = $value;
|
|
|
+ if ($is_taxonomy) {
|
|
|
+ $tax = (strpos($attribute_key, 'pa_') === 0) ? $attribute_key : 'pa_' . $attribute_key;
|
|
|
+ $term = get_term_by('slug', $value, $tax);
|
|
|
+ if ($term && !is_wp_error($term)) {
|
|
|
+ $label = $term->name;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $result[] = array(
|
|
|
+ 'value' => $value,
|
|
|
+ 'label' => $label,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ usort($result, function($a, $b) {
|
|
|
+ return strcasecmp($a['label'], $b['label']);
|
|
|
+ });
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Find variations matching the filter.
|
|
|
+ *
|
|
|
+ * @param int[] $category_ids
|
|
|
+ * @param string $attribute_key
|
|
|
+ * @param string $attribute_value
|
|
|
+ * @return array ['count' => int, 'ids' => int[], 'sample' => array[]]
|
|
|
+ */
|
|
|
+ public function find_matching_variations($category_ids, $attribute_key, $attribute_value) {
|
|
|
+ global $wpdb;
|
|
|
+
|
|
|
+ $category_ids = array_filter(array_map('intval', (array) $category_ids), function($v) { return $v > 0; });
|
|
|
+ $attribute_key = sanitize_text_field($attribute_key);
|
|
|
+ $attribute_value = sanitize_text_field($attribute_value);
|
|
|
+
|
|
|
+ if (empty($category_ids) || $attribute_key === '' || $attribute_value === '') {
|
|
|
+ return array('count' => 0, 'ids' => array(), 'sample' => array());
|
|
|
+ }
|
|
|
+
|
|
|
+ $meta_key = 'attribute_' . wc_sanitize_taxonomy_name($attribute_key);
|
|
|
+ $placeholders = implode(',', array_fill(0, count($category_ids), '%d'));
|
|
|
+
|
|
|
+ $sql = "
|
|
|
+ SELECT DISTINCT v.ID
|
|
|
+ FROM {$wpdb->posts} v
|
|
|
+ INNER JOIN {$wpdb->posts} p ON p.ID = v.post_parent
|
|
|
+ INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = v.ID
|
|
|
+ INNER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id
|
|
|
+ INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
|
|
|
+ WHERE v.post_type = 'product_variation'
|
|
|
+ AND v.post_status IN ('publish','private')
|
|
|
+ AND p.post_type = 'product'
|
|
|
+ AND tt.taxonomy = 'product_cat'
|
|
|
+ AND tt.term_id IN ($placeholders)
|
|
|
+ AND pm.meta_key = %s
|
|
|
+ AND pm.meta_value = %s
|
|
|
+ ";
|
|
|
+
|
|
|
+ $params = array_merge($category_ids, array($meta_key, $attribute_value));
|
|
|
+ $ids = $wpdb->get_col($wpdb->prepare($sql, ...$params));
|
|
|
+ $ids = $ids ? array_map('intval', $ids) : array();
|
|
|
+
|
|
|
+ // Build a sample (up to 20).
|
|
|
+ $sample = array();
|
|
|
+ $slice = array_slice($ids, 0, 20);
|
|
|
+ foreach ($slice as $id) {
|
|
|
+ $variation = wc_get_product($id);
|
|
|
+ if (!$variation) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $parent_id = $variation->get_parent_id();
|
|
|
+ $parent_name = '';
|
|
|
+ if ($parent_id) {
|
|
|
+ $parent = wc_get_product($parent_id);
|
|
|
+ if ($parent) {
|
|
|
+ $parent_name = $parent->get_name();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $sample[] = array(
|
|
|
+ 'id' => $id,
|
|
|
+ 'sku' => $variation->get_sku(),
|
|
|
+ 'name' => $variation->get_name(),
|
|
|
+ 'parent_name' => $parent_name,
|
|
|
+ 'current_price' => $variation->get_regular_price(),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return array(
|
|
|
+ 'count' => count($ids),
|
|
|
+ 'ids' => $ids,
|
|
|
+ 'sample' => $sample,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Apply new regular price to a list of variation IDs.
|
|
|
+ *
|
|
|
+ * @param int[] $variation_ids
|
|
|
+ * @param string $new_price
|
|
|
+ * @return array ['updated','failed','failed_ids','message']
|
|
|
+ */
|
|
|
+ public function apply_variant_price($variation_ids, $new_price) {
|
|
|
+ $variation_ids = array_filter(array_map('intval', (array) $variation_ids), function($v) { return $v > 0; });
|
|
|
+
|
|
|
+ // Validate price.
|
|
|
+ if (!is_numeric($new_price) || (float) $new_price < 0) {
|
|
|
+ return array(
|
|
|
+ 'updated' => 0,
|
|
|
+ 'failed' => 0,
|
|
|
+ 'failed_ids' => array(),
|
|
|
+ 'message' => __('Please enter a valid new price', 'studiou-wc-product-cat-manage'),
|
|
|
+ 'error' => true,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ $formatted = wc_format_decimal($new_price);
|
|
|
+
|
|
|
+ $updated = 0;
|
|
|
+ $failed = 0;
|
|
|
+ $failed_ids = array();
|
|
|
+ $parent_ids = array();
|
|
|
+
|
|
|
+ foreach ($variation_ids as $id) {
|
|
|
+ try {
|
|
|
+ $variation = wc_get_product($id);
|
|
|
+ if (!$variation || !$variation->is_type('variation')) {
|
|
|
+ $failed++;
|
|
|
+ $failed_ids[] = $id;
|
|
|
+ error_log('STUDIOU WC UPVP: Variation ' . $id . ' not found or not a variation');
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $variation->set_regular_price($formatted);
|
|
|
+ $variation->save();
|
|
|
+ $updated++;
|
|
|
+ $parent_id = $variation->get_parent_id();
|
|
|
+ if ($parent_id) {
|
|
|
+ $parent_ids[$parent_id] = true;
|
|
|
+ }
|
|
|
+ } catch (Exception $e) {
|
|
|
+ $failed++;
|
|
|
+ $failed_ids[] = $id;
|
|
|
+ error_log('STUDIOU WC UPVP: Exception updating variation ' . $id . ' - ' . $e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Re-sync parent variable products so price-range cache reflects the new prices.
|
|
|
+ if (!empty($parent_ids) && class_exists('WC_Product_Variable')) {
|
|
|
+ foreach (array_keys($parent_ids) as $parent_id) {
|
|
|
+ try {
|
|
|
+ WC_Product_Variable::sync($parent_id);
|
|
|
+ if (function_exists('wc_delete_product_transients')) {
|
|
|
+ wc_delete_product_transients($parent_id);
|
|
|
+ }
|
|
|
+ } catch (Exception $e) {
|
|
|
+ error_log('STUDIOU WC UPVP: Failed to sync parent ' . $parent_id . ' - ' . $e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $message = sprintf(
|
|
|
+ __('Successfully updated %d variations.', 'studiou-wc-product-cat-manage'),
|
|
|
+ $updated
|
|
|
+ );
|
|
|
+ if ($failed > 0) {
|
|
|
+ $message .= ' ' . sprintf(
|
|
|
+ __('Failed to update %d variations.', 'studiou-wc-product-cat-manage'),
|
|
|
+ $failed
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return array(
|
|
|
+ 'updated' => $updated,
|
|
|
+ 'failed' => $failed,
|
|
|
+ 'failed_ids' => $failed_ids,
|
|
|
+ 'message' => $message,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Common AJAX preflight: nonce + capability + output buffer setup.
|
|
|
+ * Returns false on failure (after sending JSON error), true on success.
|
|
|
+ */
|
|
|
+ private function upvp_preflight() {
|
|
|
+ while (ob_get_level()) {
|
|
|
+ ob_end_clean();
|
|
|
+ }
|
|
|
+ ob_start();
|
|
|
+
|
|
|
+ if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (!current_user_can('manage_woocommerce')) {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('You do not have sufficient permissions', 'studiou-wc-product-cat-manage')));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * AJAX: list variation attribute heads.
|
|
|
+ */
|
|
|
+ public function handle_upvp_get_attributes_ajax() {
|
|
|
+ if (!$this->upvp_preflight()) return;
|
|
|
+
|
|
|
+ $original_error_reporting = error_reporting();
|
|
|
+ error_reporting(0);
|
|
|
+ try {
|
|
|
+ $heads = $this->get_variation_attribute_heads();
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_success(array('attributes' => $heads));
|
|
|
+ } catch (Exception $e) {
|
|
|
+ error_log('STUDIOU WC UPVP: get_attributes failed - ' . $e->getMessage());
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => $e->getMessage()));
|
|
|
+ } finally {
|
|
|
+ error_reporting($original_error_reporting);
|
|
|
+ ob_end_clean();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * AJAX: list values for the chosen attribute (scoped to selected categories).
|
|
|
+ */
|
|
|
+ public function handle_upvp_get_attribute_values_ajax() {
|
|
|
+ if (!$this->upvp_preflight()) return;
|
|
|
+
|
|
|
+ $original_error_reporting = error_reporting();
|
|
|
+ error_reporting(0);
|
|
|
+ try {
|
|
|
+ $attribute_key = isset($_POST['attribute_key']) ? sanitize_text_field(wp_unslash($_POST['attribute_key'])) : '';
|
|
|
+ $category_ids = isset($_POST['category_ids']) ? array_map('intval', (array) $_POST['category_ids']) : array();
|
|
|
+
|
|
|
+ if ($attribute_key === '') {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('Please select an attribute', 'studiou-wc-product-cat-manage')));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $values = $this->get_variation_attribute_values($attribute_key, $category_ids);
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_success(array('values' => $values));
|
|
|
+ } catch (Exception $e) {
|
|
|
+ error_log('STUDIOU WC UPVP: get_attribute_values failed - ' . $e->getMessage());
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => $e->getMessage()));
|
|
|
+ } finally {
|
|
|
+ error_reporting($original_error_reporting);
|
|
|
+ ob_end_clean();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * AJAX: count + sample of variations matching the current filter.
|
|
|
+ */
|
|
|
+ public function handle_upvp_count_ajax() {
|
|
|
+ if (!$this->upvp_preflight()) return;
|
|
|
+
|
|
|
+ $original_error_reporting = error_reporting();
|
|
|
+ error_reporting(0);
|
|
|
+ try {
|
|
|
+ $category_ids = isset($_POST['category_ids']) ? array_map('intval', (array) $_POST['category_ids']) : array();
|
|
|
+ $attribute_key = isset($_POST['attribute_key']) ? sanitize_text_field(wp_unslash($_POST['attribute_key'])) : '';
|
|
|
+ $attribute_value = isset($_POST['attribute_value']) ? sanitize_text_field(wp_unslash($_POST['attribute_value'])) : '';
|
|
|
+
|
|
|
+ if (empty($category_ids)) {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('Please select at least one category', 'studiou-wc-product-cat-manage')));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ($attribute_key === '') {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('Please select an attribute', 'studiou-wc-product-cat-manage')));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ($attribute_value === '') {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('Please select an attribute value', 'studiou-wc-product-cat-manage')));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $result = $this->find_matching_variations($category_ids, $attribute_key, $attribute_value);
|
|
|
+
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_success(array(
|
|
|
+ 'count' => $result['count'],
|
|
|
+ 'sample' => $result['sample'],
|
|
|
+ ));
|
|
|
+ } catch (Exception $e) {
|
|
|
+ error_log('STUDIOU WC UPVP: count failed - ' . $e->getMessage());
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => $e->getMessage()));
|
|
|
+ } finally {
|
|
|
+ error_reporting($original_error_reporting);
|
|
|
+ ob_end_clean();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * AJAX: apply new regular price to all matching variations.
|
|
|
+ */
|
|
|
+ public function handle_upvp_apply_ajax() {
|
|
|
+ if (!$this->upvp_preflight()) return;
|
|
|
+
|
|
|
+ $original_error_reporting = error_reporting();
|
|
|
+ error_reporting(0);
|
|
|
+ try {
|
|
|
+ $category_ids = isset($_POST['category_ids']) ? array_map('intval', (array) $_POST['category_ids']) : array();
|
|
|
+ $attribute_key = isset($_POST['attribute_key']) ? sanitize_text_field(wp_unslash($_POST['attribute_key'])) : '';
|
|
|
+ $attribute_value = isset($_POST['attribute_value']) ? sanitize_text_field(wp_unslash($_POST['attribute_value'])) : '';
|
|
|
+ $new_price = isset($_POST['new_price']) ? sanitize_text_field(wp_unslash($_POST['new_price'])) : '';
|
|
|
+
|
|
|
+ if (empty($category_ids)) {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('Please select at least one category', 'studiou-wc-product-cat-manage')));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ($attribute_key === '' || $attribute_value === '') {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('Please select an attribute value', 'studiou-wc-product-cat-manage')));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ($new_price === '' || !is_numeric($new_price) || (float) $new_price < 0) {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('Please enter a valid new price', 'studiou-wc-product-cat-manage')));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $matching = $this->find_matching_variations($category_ids, $attribute_key, $attribute_value);
|
|
|
+ if ($matching['count'] === 0) {
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => __('No variations match the current filter.', 'studiou-wc-product-cat-manage')));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $result = $this->apply_variant_price($matching['ids'], $new_price);
|
|
|
+
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_success(array(
|
|
|
+ 'updated' => $result['updated'],
|
|
|
+ 'failed' => $result['failed'],
|
|
|
+ 'failed_ids' => $result['failed_ids'],
|
|
|
+ 'message' => $result['message'],
|
|
|
+ ));
|
|
|
+ } catch (Exception $e) {
|
|
|
+ error_log('STUDIOU WC UPVP: apply failed - ' . $e->getMessage());
|
|
|
+ ob_clean();
|
|
|
+ wp_send_json_error(array('message' => $e->getMessage()));
|
|
|
+ } finally {
|
|
|
+ error_reporting($original_error_reporting);
|
|
|
+ ob_end_clean();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|