'product', 'posts_per_page' => 20, 'paged' => $page, 'post__not_in' => $exclude, 'orderby' => 'date', 'order' => 'DESC', 'author' => $author, ]; if (!empty($search)) { $args['s'] = $search; } if (!empty($category)) { $args['tax_query'] = [ [ 'taxonomy' => 'product_cat', 'field' => 'slug', 'terms' => $category, ], ]; } $query = new WP_Query($args); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); wc_get_template_part('content', 'product'); } } wp_reset_postdata(); wp_die(); } /** * Load store posts via AJAX */ public function load_store_posts() { $page = isset($_GET['page']) ? intval($_GET['page']) : 1; $author = isset($_GET['author']) ? intval($_GET['author']) : 0; $query = new WP_Query([ 'post_type' => 'post', 'posts_per_page' => 20, 'paged' => $page, 'author' => $author, ]); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $thumb = has_post_thumbnail() ? get_the_post_thumbnail(null, 'medium') : 'No image'; echo '
  • ' . $thumb . '
    ' . get_the_title() . '
  • '; } } wp_reset_postdata(); wp_die(); } /** * Handle Like Post */ public function like_post() { if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Login required']); } check_ajax_referer('dezia_like_nonce', 'nonce'); $user_id = get_current_user_id(); $post_id = absint($_POST['post_id']); if (get_post_type($post_id) !== 'post') { wp_send_json_error(['message' => 'Invalid post']); } $liked_users = get_post_meta($post_id, '_dezia_liked_users', true); $liked_users = is_array($liked_users) ? $liked_users : []; if (in_array($user_id, $liked_users)) { wp_send_json_error(['message' => 'You already liked this post']); } $liked_users[] = $user_id; update_post_meta($post_id, '_dezia_liked_users', $liked_users); $likes_count = count($liked_users); update_post_meta($post_id, '_dezia_likes_count', $likes_count); wp_send_json_success(['likes' => $likes_count]); } /** * Load more products */ public function load_more_products() { $store_id = intval($_GET['store']); $offset = intval($_GET['offset']); echo do_shortcode('[dez_store_new_products store="' . $store_id . '" limit="20" offset="' . $offset . '"]'); wp_die(); } /** * Check domain availability */ public function check_domain() { check_ajax_referer('dezia-domain-nonce', 'nonce'); if (!current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied']); } $domain = isset($_POST['domain']) ? sanitize_text_field($_POST['domain']) : ''; $tld = isset($_POST['tld']) ? sanitize_text_field($_POST['tld']) : '.com'; if (empty($domain)) { wp_send_json_error(['message' => 'Domain name is required']); } $tld_clean = ltrim($tld, '.'); $api_key = get_option('dezia_hostinger_api_key'); if (empty($api_key)) { wp_send_json_error(['message' => 'Hostinger API key not configured. Please contact admin.']); } $body = json_encode([ 'domain' => $domain, 'tlds' => [$tld_clean], 'with_alternatives' => false ]); $response = wp_remote_post('https://developers.hostinger.com/api/domains/v1/availability', [ 'headers' => [ 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json' ], 'body' => $body, 'timeout' => 15 ]); if (is_wp_error($response)) { error_log('Hostinger API Error: ' . $response->get_error_message()); wp_send_json_error(['message' => 'Error: Unable to check domain availability.']); } $status_code = wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); $data = json_decode($response_body, true); if ($status_code === 200 && is_array($data) && !empty($data)) { $result = $data[0]; wp_send_json_success([ 'available' => $result['is_available'], 'domain' => $result['domain'], 'price' => 'Contact admin for pricing' ]); } else { $error_message = isset($data['message']) ? $data['message'] : 'Unknown error'; error_log('Hostinger API Error: ' . $error_message); wp_send_json_error(['message' => 'Unable to verify domain availability: ' . $error_message]); } } /** * Request domain */ public function request_domain() { check_ajax_referer('dezia-domain-nonce', 'nonce'); if (!current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied']); } $domain = isset($_POST['domain']) ? sanitize_text_field($_POST['domain']) : ''; if (empty($domain)) { wp_send_json_error(['message' => 'Domain name is required']); } $user_id = get_current_user_id(); $store_name = get_user_meta($user_id, 'store_name', true); $store_email = get_user_meta($user_id, 'store_email', true); $store_phone = get_user_meta($user_id, 'store_phone', true); if (empty($store_email)) { $user = wp_get_current_user(); $store_email = $user->user_email; } $to = 'operations@dezia.co'; $subject = 'New Domain Purchase Request from ' . $store_name; $body = "

    New Domain Purchase Request

    Domain: {$domain}

    Store Name: {$store_name}

    Store Email: {$store_email}

    Store Phone: {$store_phone}


    This request was submitted from the Dezia Empower plugin's Custom Domain tab.

    "; $headers = array( 'Content-Type: text/html; charset=UTF-8', 'From: Dezia Empower ' ); $sent = wp_mail($to, $subject, $body, $headers); if ($sent) { update_user_meta($user_id, 'custom_domain_requested', $domain); wp_send_json_success(['message' => 'Domain Request submitted successfully, the Dezia Team will contact you within 24hrs']); } else { wp_send_json_error(['message' => 'Unable to send request at this time. Please try again later.']); } } /** * SMS Toggle Handler */ public function handle_sms_toggle() { if (isset($_POST['nonce'])) { if (!wp_verify_nonce(sanitize_text_field($_POST['nonce']), 'dezia-sms-toggle-nonce')) { wp_send_json_error(['message' => 'Invalid nonce'], 403); } } if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Permission denied'], 403); } $smanager_id = isset($_POST['smanager_id']) ? intval($_POST['smanager_id']) : 0; $enabled = isset($_POST['enabled']) ? intval($_POST['enabled']) : 0; if ($smanager_id <= 0) { wp_send_json_error(['message' => 'Invalid SManager ID'], 400); } $result = update_user_meta($smanager_id, 'dezia_sms_notify_enabled', $enabled ? 1 : 0); if ($result === false) { wp_send_json_error(['message' => 'Failed to update setting'], 500); } wp_send_json_success(['message' => 'SMS notification setting updated']); } /** * Get SManager Details for Admin */ public function get_smanager_details() { if (!current_user_can('manage_options')) wp_send_json_error(['html' => 'Permission denied.']); $smanager_id = intval($_POST['smanager_id']); $user = get_userdata($smanager_id); if (!$user) wp_send_json_error(['html' => 'User not found.']); $banners = get_user_meta($smanager_id, 'banner_image_urls', true); $banner = (is_array($banners) && !empty($banners)) ? $banners[0] : ''; $fields = [ 'Store Name' => get_user_meta($smanager_id, 'store_name', true), 'Store Business Category' => get_user_meta($smanager_id, 'store_business_type', true), 'Store Tagline' => get_user_meta($smanager_id, 'store_tagline', true), 'Store Description' => get_user_meta($smanager_id, 'store_description', true), 'Contact Person' => get_user_meta($smanager_id, 'store_contact_person', true), 'Store Phone' => get_user_meta($smanager_id, 'store_phone', true), 'Store WhatsApp' => get_user_meta($smanager_id, 'store_whatsapp', true), 'Store Email' => get_user_meta($smanager_id, 'store_email', true), 'Store Address' => get_user_meta($smanager_id, 'store_address', true), 'Store City' => get_user_meta($smanager_id, 'store_city', true), 'Store State' => get_user_meta($smanager_id, 'store_state', true), 'Store Country' => get_user_meta($smanager_id, 'store_country', true), 'Location Latitude' => get_user_meta($smanager_id, 'dezia_smanager_lat', true), 'Location Longitude' => get_user_meta($smanager_id, 'dezia_smanager_lng', true), 'Custom Domain Requested' => get_user_meta($smanager_id, 'custom_domain_requested', true), 'Preferred Payout Method' => get_user_meta($smanager_id, 'payment_method', true), 'Mobile Money Network' => get_user_meta($smanager_id, 'momo_network', true), 'Mobile Money Number' => get_user_meta($smanager_id, 'momo_number', true), 'MoMo Account Holder Name' => get_user_meta($smanager_id, 'momo_name', true), 'Bank Account Name' => get_user_meta($smanager_id, 'bank_account_name', true), 'Bank Name' => get_user_meta($smanager_id, 'bank_provider', true), 'Bank Account Number' => get_user_meta($smanager_id, 'bank_account_number', true), 'Store Logo' => get_user_meta($smanager_id, 'store_logo', true), 'Store Banner' => $banner, ]; wp_die(); } /** * Increment post view count */ public function dezia_view_post() { check_ajax_referer('dezia-view-nonce', 'nonce'); $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; if (!$post_id) { wp_send_json_error(['message' => 'Invalid post ID']); } $views = (int) get_post_meta($post_id, '_post_views_count', true); $views++; update_post_meta($post_id, '_post_views_count', $views); wp_send_json_success(['views' => $views]); } /** * Upload product image via AJAX */ public function upload_product_image() { if (!current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied'], 403); } if (empty($_FILES['file'])) { wp_send_json_error(['message' => 'No file uploaded'], 400); } require_once(ABSPATH . 'wp-admin/includes/image.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); // Pass 0 to prevent WordPress from checking 'edit_post' permissions on draft post IDs, // which can fail for custom roles like 'smanager' during media handling. $attachment_id = media_handle_upload('file', 0); if (is_wp_error($attachment_id)) { wp_send_json_error(['message' => $attachment_id->get_error_message()], 400); } $attachment_url = wp_get_attachment_url($attachment_id); wp_send_json_success([ 'id' => $attachment_id, 'url' => $attachment_url ]); } /** * Save product draft via AJAX */ public function save_product_draft() { if (!current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied'], 403); } $user_id = get_current_user_id(); $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0; $title = isset($_POST['product_title']) ? sanitize_text_field($_POST['product_title']) : ''; $price = isset($_POST['product_price']) ? floatval($_POST['product_price']) : 0; $sale_price = isset($_POST['product_sale_price']) && $_POST['product_sale_price'] !== '' ? floatval($_POST['product_sale_price']) : ''; $featured_image_id = isset($_POST['featured_image_id']) ? intval($_POST['featured_image_id']) : 0; $gallery_ids_str = isset($_POST['product_gallery_ids']) ? sanitize_text_field($_POST['product_gallery_ids']) : ''; $product_type = isset($_POST['product_type']) ? sanitize_text_field($_POST['product_type']) : 'simple'; $description = isset($_POST['product_description']) ? wp_kses_post($_POST['product_description']) : ''; $short_description = isset($_POST['product_short_description']) ? sanitize_text_field($_POST['product_short_description']) : ''; $sku = isset($_POST['product_sku']) ? sanitize_text_field($_POST['product_sku']) : ''; $slug = isset($_POST['product_slug']) ? sanitize_title($_POST['product_slug']) : ''; $categories = isset($_POST['product_categories']) ? array_map('intval', (array)$_POST['product_categories']) : []; $manage_stock = isset($_POST['manage_stock']) ? sanitize_text_field($_POST['manage_stock']) : 'no'; $stock_quantity = isset($_POST['product_stock']) ? intval($_POST['product_stock']) : 0; $low_stock_amount = isset($_POST['low_stock_amount']) ? intval($_POST['low_stock_amount']) : ''; // If product_id exists, verify author is current user if ($product_id) { $post = get_post($product_id); if (!$post || $post->post_author != $user_id || $post->post_type !== 'product') { wp_send_json_error(['message' => 'Invalid product ID'], 400); } } $post_data = [ 'post_title' => !empty($title) ? $title : 'Draft Product', 'post_content' => $description, 'post_excerpt' => $short_description, 'post_status' => 'draft', 'post_type' => 'product', 'post_author' => $user_id, ]; if (!empty($slug)) { $post_data['post_name'] = $slug; } if ($product_id) { $post_data['ID'] = $product_id; wp_update_post($post_data); } else { $product_id = wp_insert_post($post_data); } if (!$product_id || is_wp_error($product_id)) { wp_send_json_error(['message' => 'Failed to save draft'], 500); } if (!empty($sku)) { update_post_meta($product_id, '_sku', $sku); } // Set Product Type and Save using WC CRUD API to sync lookup tables and properties if ($product_type === 'variable') { wp_set_object_terms($product_id, 'variable', 'product_type'); if (class_exists('WC_Product_Variable')) { $new_product = new WC_Product_Variable($product_id); $new_product->save(); } } else { wp_set_object_terms($product_id, 'simple', 'product_type'); if (class_exists('WC_Product_Simple')) { $new_product = new WC_Product_Simple($product_id); $new_product->save(); } } // Set Categories if (!empty($categories)) { wp_set_object_terms($product_id, $categories, 'product_cat'); } // Featured Image if ($featured_image_id) { set_post_thumbnail($product_id, $featured_image_id); } else { delete_post_thumbnail($product_id); } // Gallery if ($gallery_ids_str !== '') { $gallery_ids = array_map('intval', array_filter(explode(',', $gallery_ids_str))); update_post_meta($product_id, '_product_image_gallery', implode(',', $gallery_ids)); } else { delete_post_meta($product_id, '_product_image_gallery'); } // Pricing & Inventory update_post_meta($product_id, '_regular_price', $price); if ($sale_price !== '' && $sale_price < $price && $sale_price > 0) { update_post_meta($product_id, '_sale_price', $sale_price); update_post_meta($product_id, '_price', $sale_price); } else { delete_post_meta($product_id, '_sale_price'); update_post_meta($product_id, '_price', $price); } if ($product_type === 'variable') { if ($manage_stock === 'yes') { // Parent-level shared inventory pool update_post_meta($product_id, '_manage_stock', 'yes'); update_post_meta($product_id, '_stock', $stock_quantity); update_post_meta($product_id, '_stock_status', $stock_quantity > 0 ? 'instock' : 'outofstock'); } else { // Variation-level inventory update_post_meta($product_id, '_manage_stock', 'no'); delete_post_meta($product_id, '_stock'); delete_post_meta($product_id, '_low_stock_amount'); } } else { update_post_meta($product_id, '_manage_stock', $manage_stock); if ($manage_stock === 'yes') { update_post_meta($product_id, '_stock', $stock_quantity); update_post_meta($product_id, '_stock_status', $stock_quantity > 0 ? 'instock' : 'outofstock'); if ($low_stock_amount !== '') { update_post_meta($product_id, '_low_stock_amount', $low_stock_amount); } } else { delete_post_meta($product_id, '_stock'); update_post_meta($product_id, '_stock_status', 'instock'); delete_post_meta($product_id, '_low_stock_amount'); } } // Product Options (Attributes) for variable products $raw_options = isset($_POST['product_options']) ? $_POST['product_options'] : []; if (is_string($raw_options)) { $decoded_opts = json_decode(stripslashes($raw_options), true); if (is_array($decoded_opts)) { $raw_options = $decoded_opts; } } if ($product_type === 'variable' && !empty($raw_options)) { $product_attributes = get_post_meta($product_id, '_product_attributes', true); if (!is_array($product_attributes)) { $product_attributes = []; } $position = 0; $saved_custom_attrs = get_user_meta($user_id, 'dezia_smanager_custom_attributes', true); if (!is_array($saved_custom_attrs)) { $saved_custom_attrs = []; } foreach ($raw_options as $option) { if (empty($option['name']) || empty($option['values'])) continue; $opt_name = sanitize_text_field($option['name']); $opt_values_raw = is_array($option['values']) ? $option['values'] : explode('|', (string)$option['values']); $opt_values = array_filter(array_map('sanitize_text_field', array_map('trim', $opt_values_raw))); $attr_key = sanitize_title($opt_name); $is_taxonomy = taxonomy_exists('pa_' . $attr_key) || taxonomy_exists($opt_name); $product_attributes[$attr_key] = [ 'name' => $opt_name, 'value' => implode(' | ', $opt_values), 'position' => $position, 'is_visible' => 1, 'is_variation' => 1, 'is_taxonomy' => $is_taxonomy ? 1 : 0, ]; $position++; if (!in_array($opt_name, $saved_custom_attrs, true)) { $saved_custom_attrs[] = $opt_name; } } update_post_meta($product_id, '_product_attributes', $product_attributes); update_user_meta($user_id, 'dezia_smanager_custom_attributes', array_values(array_unique($saved_custom_attrs))); if (!empty($product_attributes) && class_exists('WC_Product_Attribute') && class_exists('WC_Product_Variable')) { $wc_attributes = []; foreach ($product_attributes as $attr_key => $attr_data) { $attribute = new \WC_Product_Attribute(); $attribute->set_name($attr_data['name']); $attribute->set_options(explode(' | ', $attr_data['value'])); $attribute->set_position($attr_data['position']); $attribute->set_visible(true); $attribute->set_variation(true); $wc_attributes[] = $attribute; } $wc_var_prod = new \WC_Product_Variable($product_id); $wc_var_prod->set_attributes($wc_attributes); $wc_var_prod->save(); } // Handle Variations $raw_variations = isset($_POST['variations']) ? $_POST['variations'] : []; if (is_string($raw_variations)) { $decoded_vars = json_decode(stripslashes($raw_variations), true); if (is_array($decoded_vars)) { $raw_variations = $decoded_vars; } } if (!empty($raw_variations)) { $existing_variations = get_posts([ 'post_parent' => $product_id, 'post_type' => 'product_variation', 'numberposts' => -1, 'fields' => 'ids', ]); $has_instock_variation = false; foreach ($raw_variations as $idx => $variation) { $v_id_sub = isset($variation['id']) ? intval($variation['id']) : 0; $var_price = isset($variation['price']) ? floatval($variation['price']) : (isset($variation['regular_price']) ? floatval($variation['regular_price']) : 0); $var_sale_price = isset($variation['sale_price']) && $variation['sale_price'] !== '' ? floatval($variation['sale_price']) : ''; $var_stock = isset($variation['stock']) ? intval($variation['stock']) : 0; $var_image_id = isset($variation['image_id']) ? intval($variation['image_id']) : 0; if ($v_id_sub > 0 && in_array($v_id_sub, $existing_variations, true)) { $variation_id = $v_id_sub; wp_update_post([ 'ID' => $variation_id, 'post_title' => sprintf('Variation #%d of %s', $idx + 1, !empty($title) ? $title : 'Product'), 'menu_order' => $idx, ]); } else { $variation_id = wp_insert_post([ 'post_title' => sprintf('Variation #%d of %s', $idx + 1, !empty($title) ? $title : 'Product'), 'post_status' => 'publish', 'post_parent' => $product_id, 'post_type' => 'product_variation', 'menu_order' => $idx, ]); } if ($variation_id && !is_wp_error($variation_id)) { $var_obj = new WC_Product_Variation($variation_id); $var_obj->set_regular_price($var_price); update_post_meta($variation_id, '_regular_price', $var_price); if ($var_sale_price !== '' && floatval($var_sale_price) < $var_price && floatval($var_sale_price) > 0) { $var_obj->set_sale_price(floatval($var_sale_price)); $var_obj->set_price(floatval($var_sale_price)); update_post_meta($variation_id, '_sale_price', floatval($var_sale_price)); update_post_meta($variation_id, '_price', floatval($var_sale_price)); } else { $var_obj->set_sale_price(''); $var_obj->set_price($var_price); delete_post_meta($variation_id, '_sale_price'); update_post_meta($variation_id, '_price', $var_price); } if ($manage_stock === 'yes') { $var_obj->set_manage_stock(false); $var_status = $stock_quantity > 0 ? 'instock' : 'outofstock'; $var_obj->set_stock_status($var_status); update_post_meta($variation_id, '_manage_stock', 'no'); update_post_meta($variation_id, '_stock_status', $var_status); delete_post_meta($variation_id, '_stock'); if ($stock_quantity > 0) { $has_instock_variation = true; } } else { $var_obj->set_manage_stock(true); $var_obj->set_stock_quantity($var_stock); $var_status = $var_stock > 0 ? 'instock' : 'outofstock'; $var_obj->set_stock_status($var_status); update_post_meta($variation_id, '_manage_stock', 'yes'); update_post_meta($variation_id, '_stock', $var_stock); update_post_meta($variation_id, '_stock_status', $var_status); if ($var_stock > 0) { $has_instock_variation = true; } } if ($var_image_id > 0) { $var_obj->set_image_id($var_image_id); set_post_thumbnail($variation_id, $var_image_id); } else if ($var_image_id === 0) { delete_post_thumbnail($variation_id); } // Associate variation with custom/taxonomy attributes $submitted_attributes = []; $var_attrs_input = isset($variation['attributes']) ? $variation['attributes'] : ''; if (is_string($var_attrs_input)) { $decoded_attrs = json_decode(stripslashes($var_attrs_input), true); if (is_array($decoded_attrs)) { $var_attrs_input = $decoded_attrs; } } if (is_string($var_attrs_input)) { $var_attrs_arr = explode('|', $var_attrs_input); $attr_index = 0; foreach ($product_attributes as $key => $attr_meta) { $term = isset($var_attrs_arr[$attr_index]) ? sanitize_text_field(trim($var_attrs_arr[$attr_index])) : ''; if ($term !== '') { $taxonomy_key = taxonomy_exists('pa_' . $key) ? 'attribute_pa_' . $key : 'attribute_' . $key; $submitted_attributes[$taxonomy_key] = $term; } $attr_index++; } } elseif (is_array($var_attrs_input)) { foreach ($var_attrs_input as $attr_k => $attr_v) { $clean_k = str_starts_with($attr_k, 'attribute_') ? $attr_k : 'attribute_' . sanitize_title($attr_k); $submitted_attributes[$clean_k] = sanitize_text_field($attr_v); } } $current_wc_attrs = $var_obj->get_attributes(); $final_attributes = []; if (!empty($submitted_attributes)) { foreach ($submitted_attributes as $s_meta_key => $s_meta_val) { $raw_key = str_starts_with($s_meta_key, 'attribute_') ? substr($s_meta_key, 10) : $s_meta_key; $tax_name = str_starts_with($raw_key, 'pa_') ? $raw_key : 'pa_' . $raw_key; $clean_val = $s_meta_val; if (taxonomy_exists($tax_name)) { $term = get_term_by('name', $s_meta_val, $tax_name); if (!$term || is_wp_error($term)) { $term = get_term_by('slug', sanitize_title($s_meta_val), $tax_name); } if ($term && !is_wp_error($term)) { $clean_val = $term->slug; } else { $clean_val = sanitize_title($s_meta_val); } } $final_attributes[$s_meta_key] = $clean_val; update_post_meta($variation_id, $s_meta_key, $clean_val); if (str_starts_with($s_meta_key, 'attribute_pa_')) { update_post_meta($variation_id, 'attribute_' . str_replace('attribute_', '', $s_meta_key), $clean_val); } } } if (empty($final_attributes) && !empty($current_wc_attrs)) { foreach ($current_wc_attrs as $c_key => $c_val) { $meta_k = str_starts_with($c_key, 'attribute_') ? $c_key : 'attribute_' . $c_key; $final_attributes[$meta_k] = $c_val; update_post_meta($variation_id, $meta_k, $c_val); } } if (!empty($final_attributes)) { $var_obj->set_attributes($final_attributes); } $var_obj->save(); if (!empty($variation['delete'])) { wp_delete_post($variation_id, true); } } } if ($manage_stock !== 'yes') { update_post_meta($product_id, '_stock_status', $has_instock_variation ? 'instock' : 'outofstock'); } if (class_exists('WC_Product_Variable')) { \WC_Product_Variable::sync($product_id); } } } wc_delete_product_transients($product_id); wp_send_json_success([ 'product_id' => $product_id, 'message' => 'Draft saved successfully' ]); } /** * Publish product via AJAX */ public function publish_product() { if (!current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied'], 403); } $user_id = get_current_user_id(); $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0; if (!$product_id) { wp_send_json_error(['message' => 'Product ID is required'], 400); } $post = get_post($product_id); if (!$post || $post->post_author != $user_id || $post->post_type !== 'product') { wp_send_json_error(['message' => 'Invalid product ID'], 400); } // Use WooCommerce CRUD API to publish and save the product. // This automatically handles post status transitions, metadata lookup tables, // variation syncs, and clears the transient caches natively. $product = wc_get_product($product_id); if (!$product) { wp_send_json_error(['message' => 'Product not found'], 400); } $product->set_status('publish'); $product->save(); if ($product->is_type('variable')) { if (class_exists('WC_Product_Variable')) { WC_Product_Variable::sync($product_id); } } wc_delete_product_transients($product_id); $product_url = get_permalink($product_id); wp_send_json_success([ 'product_id' => $product_id, 'product_url' => $product_url, 'message' => 'Product published successfully!' ]); } /** * Create custom category via AJAX */ public function create_custom_category() { if (!current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied'], 403); } $cat_name = isset($_POST['category_name']) ? sanitize_text_field($_POST['category_name']) : ''; if (empty($cat_name)) { wp_send_json_error(['message' => 'Category name is required'], 400); } // Insert term into product_cat taxonomy $term = wp_insert_term($cat_name, 'product_cat'); delete_transient('dezia_product_cat_terms'); if (is_wp_error($term)) { if ($term->get_error_code() === 'term_exists') { $term_id = $term->get_error_data(); wp_send_json_success([ 'term_id' => intval($term_id), 'name' => $cat_name, 'message' => 'Category already exists' ]); } wp_send_json_error(['message' => $term->get_error_message()], 400); } wp_send_json_success([ 'term_id' => intval($term['term_id']), 'name' => $cat_name, 'message' => 'Category created successfully!' ]); } /** * Handle AJAX Add to Cart for Single Product Page */ public function dezia_add_to_cart() { if (!function_exists('WC') || !WC()->cart) { wp_send_json_error('WooCommerce is not active.', 400); } $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0; $variation_id = isset($_POST['variation_id']) ? intval($_POST['variation_id']) : 0; $quantity = isset($_POST['quantity']) ? intval($_POST['quantity']) : 1; if (!$product_id) { wp_send_json_error('Invalid product ID.', 400); } $passed = apply_filters('woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id); if ($passed) { $cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id); if ($cart_item_key) { wp_send_json_success([ 'cart_item_key' => $cart_item_key, 'cart_count' => WC()->cart->get_cart_contents_count(), 'checkout_url' => wc_get_checkout_url() ]); } } wp_send_json_error('Could not add product to cart.', 400); } /** * Handle AJAX Product Review Submission */ public function dezia_submit_product_review() { $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0; $rating = isset($_POST['rating']) ? intval($_POST['rating']) : 5; $comment = isset($_POST['comment']) ? sanitize_textarea_field($_POST['comment']) : ''; if (!$product_id || empty($comment)) { wp_send_json_error('Please fill in all required review fields.', 400); } if (is_user_logged_in()) { $user = wp_get_current_user(); $author_name = $user->display_name; $author_email = $user->user_email; $user_id = $user->ID; } else { $author_name = isset($_POST['author_name']) ? sanitize_text_field($_POST['author_name']) : 'Anonymous'; $author_email = isset($_POST['author_email']) ? sanitize_email($_POST['author_email']) : ''; $user_id = 0; if (empty($author_name) || empty($author_email)) { wp_send_json_error('Name and email are required for review submission.', 400); } } $commentdata = [ 'comment_post_ID' => $product_id, 'comment_author' => $author_name, 'comment_author_email' => $author_email, 'comment_content' => $comment, 'comment_type' => 'review', 'comment_approved' => 1, 'user_id' => $user_id, 'comment_date' => current_time('mysql'), ]; $comment_id = wp_insert_comment($commentdata); if ($comment_id) { update_comment_meta($comment_id, 'rating', $rating); if (function_exists('wc_delete_product_transients')) { wc_delete_product_transients($product_id); } $is_verified = function_exists('wc_customer_bought_product') ? wc_customer_bought_product($author_email, $user_id, $product_id) : false; $stars_html = ''; for ($i = 1; $i <= 5; $i++) { $stars_html .= $i <= $rating ? '★' : '☆'; } $verified_html = $is_verified ? 'Verified Purchase' : ''; $html = '
    ' . '
    ' . '
    ' . esc_html($author_name) . $verified_html . '
    ' . 'Just now' . '
    ' . '
    ' . $stars_html . '
    ' . '
    ' . esc_html($comment) . '
    ' . '
    '; wp_send_json_success(['html' => $html, 'comment_id' => $comment_id]); } wp_send_json_error('Failed to post review.', 500); } /** * Delete product via AJAX for SManagers */ public function dezia_delete_product() { $nonce = isset($_POST['nonce']) ? sanitize_text_field($_POST['nonce']) : (isset($_POST['security']) ? sanitize_text_field($_POST['security']) : ''); if (!wp_verify_nonce($nonce, 'dezia_delete_product_nonce')) { wp_send_json_error(['message' => 'Security check failed. Please refresh the page.'], 403); } if (!is_user_logged_in() || !current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied. You must be an authorized Store Manager.'], 403); } $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0; if ($product_id <= 0) { wp_send_json_error(['message' => 'Invalid product ID.'], 400); } $post = get_post($product_id); if (!$post || $post->post_type !== 'product') { wp_send_json_error(['message' => 'Product not found.'], 404); } $user_id = get_current_user_id(); if ((int) $post->post_author !== (int) $user_id && !current_user_can('administrator')) { wp_send_json_error(['message' => 'You do not have permission to delete this product.'], 403); } $product = wc_get_product($product_id); if ($product) { $deleted = $product->delete(true); } else { $deleted = wp_delete_post($product_id, true); } if ($deleted) { wp_send_json_success([ 'message' => 'Product successfully deleted.', 'product_id' => $product_id ]); } else { wp_send_json_error(['message' => 'Failed to delete product.'], 500); } } /** * Get product details for editing via AJAX */ public function dezia_get_product_for_edit() { if (!is_user_logged_in() || !current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied'], 403); } $product_id = isset($_REQUEST['product_id']) ? intval($_REQUEST['product_id']) : 0; if ($product_id <= 0) { wp_send_json_error(['message' => 'Invalid product ID'], 400); } $user_id = get_current_user_id(); $post = get_post($product_id); if (!$post || $post->post_type !== 'product') { wp_send_json_error(['message' => 'Product not found'], 404); } if ((int) $post->post_author !== (int) $user_id && !current_user_can('administrator')) { wp_send_json_error(['message' => 'Permission denied'], 403); } $product = wc_get_product($product_id); if (!$product) { wp_send_json_error(['message' => 'Could not load product'], 400); } $is_variable = $product->is_type('variable'); $featured_image_id = get_post_thumbnail_id($product_id); $featured_image_url = $featured_image_id ? wp_get_attachment_url($featured_image_id) : ''; $gallery_ids_csv = get_post_meta($product_id, '_product_image_gallery', true); $gallery_ids = $gallery_ids_csv ? array_filter(array_map('intval', explode(',', $gallery_ids_csv))) : []; $gallery_images = []; foreach ($gallery_ids as $g_id) { $url = wp_get_attachment_url($g_id); if ($url) { $gallery_images[] = ['id' => $g_id, 'url' => $url]; } } $categories = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'ids']); // Custom attributes for variable product $product_options = []; $pa_meta = get_post_meta($product_id, '_product_attributes', true); if (is_array($pa_meta)) { foreach ($pa_meta as $key => $attr) { if (empty($attr['name'])) continue; $opt_name = $attr['name']; $opt_values = []; if (!empty($attr['is_taxonomy']) && taxonomy_exists($attr['name'])) { $terms = wp_get_post_terms($product_id, $attr['name'], ['fields' => 'names']); if (!is_wp_error($terms) && !empty($terms)) { $opt_values = $terms; } } elseif (isset($attr['value']) && $attr['value'] !== '') { $opt_values = array_map('trim', explode('|', $attr['value'])); } if (!empty($opt_values)) { $label_name = function_exists('wc_attribute_label') ? wc_attribute_label($opt_name) : $opt_name; $product_options[] = [ 'name' => !empty($label_name) ? $label_name : $opt_name, 'values' => array_values(array_unique(array_filter($opt_values))) ]; } } } $variation_ids = $product->get_children(); if (empty($variation_ids)) { $variation_ids = get_posts([ 'post_parent' => $product_id, 'post_type' => 'product_variation', 'post_status' => ['publish', 'private', 'draft'], 'numberposts' => -1, 'fields' => 'ids', ]); } $is_variable = $product->is_type('variable') || !empty($variation_ids) || !empty($product_options); // Variations data $variations = []; if (!empty($variation_ids)) { foreach ($variation_ids as $v_id) { $v = wc_get_product($v_id); if (!$v) continue; $v_attributes = $v->get_attributes(); $label_parts = []; $formatted_attributes = []; if (is_array($v_attributes) && !empty($v_attributes)) { foreach ($v_attributes as $attr_key => $attr_val) { if ($attr_val === '' || $attr_val === null) continue; $clean_key = str_replace(['attribute_', 'pa_'], '', $attr_key); $tax_name = str_starts_with($attr_key, 'attribute_') ? str_replace('attribute_', '', $attr_key) : (str_starts_with($attr_key, 'pa_') ? $attr_key : 'pa_' . $attr_key); $attr_label = $clean_key; $attr_value_name = $attr_val; if (taxonomy_exists($tax_name)) { if (function_exists('wc_attribute_label')) { $attr_label = wc_attribute_label($tax_name); } $term = get_term_by('slug', $attr_val, $tax_name); if ($term && !is_wp_error($term)) { $attr_value_name = $term->name; } } elseif (isset($pa_meta[$clean_key]['name'])) { $attr_label = $pa_meta[$clean_key]['name']; } $label_parts[] = ucfirst($attr_label) . ': ' . $attr_value_name; $formatted_attributes[$attr_key] = $attr_value_name; } } $canonical_attributes = is_array($v_attributes) ? $v_attributes : []; if (empty($canonical_attributes) && is_array($pa_meta)) { foreach ($pa_meta as $key => $attr_meta) { $tax_key = taxonomy_exists('pa_' . $key) ? 'attribute_pa_' . $key : 'attribute_' . $key; $val = get_post_meta($v_id, $tax_key, true); if ($val === '') { $val = get_post_meta($v_id, 'attribute_' . $key, true); } if ($val === '') { $val = get_post_meta($v_id, 'attribute_' . sanitize_title($attr_meta['name']), true); } if ($val !== '') { $label_parts[] = $attr_meta['name'] . ': ' . $val; $canonical_attributes[$tax_key] = $val; } } } $v_label = !empty($label_parts) ? implode(' / ', $label_parts) : sprintf('Variation #%d', $v_id); $v_img_id = $v->get_image_id() ? $v->get_image_id() : get_post_thumbnail_id($v_id); if (!$v_img_id) { $v_img_id = $featured_image_id; } $v_img_url = $v_img_id ? wp_get_attachment_image_url($v_img_id, 'thumbnail') : ''; if (!$v_img_url && function_exists('wc_placeholder_img_src')) { $v_img_url = wc_placeholder_img_src('thumbnail'); } if (!$v_img_url) { $v_img_url = '/wp-content/plugins/woocommerce/assets/images/placeholder.png'; } $reg_price = $v->get_regular_price(); $price_val = $v->get_price(); $sale_price = $v->get_sale_price(); $stock_val = $v->get_stock_quantity() !== null ? $v->get_stock_quantity() : (get_post_meta($v_id, '_stock', true) !== '' ? get_post_meta($v_id, '_stock', true) : 0); $stock_stat = $v->get_stock_status() ?: 'instock'; $variations[] = [ 'id' => $v_id, 'label' => $v_label, 'display_name' => $v_label, 'attributes' => !empty($canonical_attributes) ? $canonical_attributes : (is_array($v_attributes) ? $v_attributes : []), 'regular_price' => $reg_price !== '' ? $reg_price : $price_val, 'price' => $price_val !== '' ? $price_val : $reg_price, 'sale_price' => $sale_price, 'stock' => $stock_val !== '' ? $stock_val : 0, 'stock_quantity' => $stock_val !== '' ? $stock_val : 0, 'stock_status' => $stock_stat, 'image_id' => $v_img_id ? intval($v_img_id) : 0, 'image_url' => $v_img_url, ]; } } $product_data = [ 'id' => $product_id, 'title' => $product->get_name(), 'description' => $post->post_content, 'short_description' => $post->post_excerpt, 'sku' => $product->get_sku(), 'slug' => $post->post_name, 'status' => $post->post_status, 'status_label' => ucfirst($post->post_status), 'type' => $product->get_type(), 'is_variable' => $is_variable, 'regular_price' => $product->get_regular_price(), 'sale_price' => $product->get_sale_price(), 'manage_stock' => $product->get_manage_stock() ? 'yes' : 'no', 'stock_qty' => $product->get_stock_quantity() !== null ? $product->get_stock_quantity() : 0, 'stock_status' => $product->get_stock_status() ? $product->get_stock_status() : 'instock', 'permalink' => get_permalink($product_id), 'featured_image' => ['id' => $featured_image_id, 'url' => $featured_image_url], 'gallery_images' => $gallery_images, 'categories' => $categories, 'product_options' => $product_options, 'variations' => $variations, ]; wp_send_json_success(['product' => $product_data]); } /** * Update product via AJAX for 5-tab edit suite */ public function dezia_update_product() { if (!is_user_logged_in() || !current_user_can('smanager')) { wp_send_json_error(['message' => 'Permission denied'], 403); } $user_id = get_current_user_id(); $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0; if ($product_id <= 0) { wp_send_json_error(['message' => 'Invalid product ID'], 400); } $post = get_post($product_id); if (!$post || $post->post_type !== 'product') { wp_send_json_error(['message' => 'Product not found'], 404); } if ((int) $post->post_author !== (int) $user_id && !current_user_can('administrator')) { wp_send_json_error(['message' => 'Permission denied'], 403); } // Verify Nonce $nonce = isset($_POST['dezia_edit_product_nonce']) ? $_POST['dezia_edit_product_nonce'] : (isset($_POST['nonce']) ? $_POST['nonce'] : ''); if (!wp_verify_nonce($nonce, 'dezia_edit_product') && !wp_verify_nonce($nonce, 'dezia_nonce')) { wp_send_json_error(['message' => 'Security check failed. Invalid or expired nonce.'], 403); } // Collect fields $title = isset($_POST['post_title']) ? sanitize_text_field($_POST['post_title']) : (isset($_POST['title']) ? sanitize_text_field($_POST['title']) : ''); $description = isset($_POST['post_content']) ? wp_kses_post($_POST['post_content']) : (isset($_POST['description']) ? wp_kses_post($_POST['description']) : ''); $short_description = isset($_POST['post_excerpt']) ? sanitize_textarea_field($_POST['post_excerpt']) : (isset($_POST['short_description']) ? sanitize_textarea_field($_POST['short_description']) : ''); $slug = isset($_POST['post_name']) ? sanitize_title($_POST['post_name']) : (isset($_POST['slug']) ? sanitize_title($_POST['slug']) : ''); $sku = isset($_POST['sku']) ? sanitize_text_field($_POST['sku']) : ''; $categories = isset($_POST['product_cat']) ? (array)$_POST['product_cat'] : (isset($_POST['categories']) ? (array)$_POST['categories'] : []); $categories = array_map('intval', array_filter($categories)); $featured_image_id = isset($_POST['featured_image_id']) ? intval($_POST['featured_image_id']) : 0; $gallery_ids_input = isset($_POST['gallery_ids']) ? $_POST['gallery_ids'] : (isset($_POST['product_gallery_ids']) ? $_POST['product_gallery_ids'] : ''); if (is_array($gallery_ids_input)) { $gallery_ids = array_map('intval', array_filter($gallery_ids_input)); } else { $gallery_ids = array_map('intval', array_filter(explode(',', (string)$gallery_ids_input))); } $product_type = isset($_POST['product_type']) ? sanitize_text_field($_POST['product_type']) : 'simple'; $regular_price = isset($_POST['regular_price']) && $_POST['regular_price'] !== '' ? floatval($_POST['regular_price']) : 0; $sale_price = isset($_POST['sale_price']) && $_POST['sale_price'] !== '' ? floatval($_POST['sale_price']) : ''; $manage_stock = isset($_POST['manage_stock']) ? sanitize_text_field($_POST['manage_stock']) : 'no'; if ($manage_stock === '1' || $manage_stock === 'true' || $manage_stock === 'on') { $manage_stock = 'yes'; } $stock_qty = isset($_POST['stock_qty']) ? intval($_POST['stock_qty']) : (isset($_POST['stock_quantity']) ? intval($_POST['stock_quantity']) : 0); $stock_status = isset($_POST['stock_status']) ? sanitize_text_field($_POST['stock_status']) : 'instock'; // Update main post $post_data = [ 'ID' => $product_id, 'post_content' => $description, 'post_excerpt' => $short_description, ]; if (!empty($title)) { $post_data['post_title'] = $title; } if (!empty($slug)) { $post_data['post_name'] = $slug; } wp_update_post($post_data); // Categories wp_set_object_terms($product_id, $categories, 'product_cat'); // Featured Image & Gallery if ($featured_image_id > 0) { set_post_thumbnail($product_id, $featured_image_id); } else if ($featured_image_id === 0) { delete_post_thumbnail($product_id); } if (!empty($gallery_ids)) { update_post_meta($product_id, '_product_image_gallery', implode(',', $gallery_ids)); } else { delete_post_meta($product_id, '_product_image_gallery'); } // SKU update_post_meta($product_id, '_sku', $sku); // Product Type if ($product_type === 'variable') { wp_set_object_terms($product_id, 'variable', 'product_type'); if (class_exists('WC_Product_Variable')) { $wc_prod = new WC_Product_Variable($product_id); $wc_prod->save(); } // Clear parent simple product price meta to let WooCommerce sync dynamic variation prices delete_post_meta($product_id, '_regular_price'); delete_post_meta($product_id, '_sale_price'); } else { wp_set_object_terms($product_id, 'simple', 'product_type'); if (class_exists('WC_Product_Simple')) { $wc_prod = new WC_Product_Simple($product_id); $wc_prod->save(); } // Delete old child variations if switching from Variable to Simple to prevent orphan variation conflicts $old_variations = get_posts([ 'post_parent' => $product_id, 'post_type' => 'product_variation', 'numberposts' => -1, 'fields' => 'ids', ]); if (!empty($old_variations)) { foreach ($old_variations as $old_v_id) { wp_delete_post($old_v_id, true); } } delete_post_meta($product_id, '_product_attributes'); } // Pricing & Inventory for Simple vs Variable if ($product_type === 'simple') { $wc_prod = wc_get_product($product_id); if (!$wc_prod || !$wc_prod->is_type('simple')) { $wc_prod = new WC_Product_Simple($product_id); } $wc_prod->set_regular_price($regular_price); update_post_meta($product_id, '_regular_price', $regular_price); if ($sale_price !== '' && floatval($sale_price) < $regular_price && floatval($sale_price) > 0) { $wc_prod->set_sale_price(floatval($sale_price)); $wc_prod->set_price(floatval($sale_price)); update_post_meta($product_id, '_sale_price', floatval($sale_price)); update_post_meta($product_id, '_price', floatval($sale_price)); } else { $wc_prod->set_sale_price(''); $wc_prod->set_price($regular_price); delete_post_meta($product_id, '_sale_price'); update_post_meta($product_id, '_price', $regular_price); } $wc_prod->set_manage_stock($manage_stock === 'yes'); update_post_meta($product_id, '_manage_stock', $manage_stock); if ($manage_stock === 'yes') { $wc_prod->set_stock_quantity($stock_qty); $final_stock_status = $stock_qty > 0 ? $stock_status : 'outofstock'; $wc_prod->set_stock_status($final_stock_status); update_post_meta($product_id, '_stock', $stock_qty); update_post_meta($product_id, '_stock_status', $final_stock_status); } else { $wc_prod->set_stock_status($stock_status); $wc_prod->set_stock_quantity(null); delete_post_meta($product_id, '_stock'); update_post_meta($product_id, '_stock_status', $stock_status); } $wc_prod->save(); } else { update_post_meta($product_id, '_manage_stock', $manage_stock); if ($manage_stock === 'yes') { update_post_meta($product_id, '_stock', $stock_qty); update_post_meta($product_id, '_stock_status', $stock_qty > 0 ? $stock_status : 'outofstock'); } else { update_post_meta($product_id, '_stock_status', 'instock'); } // Attributes / Options $raw_options = isset($_POST['product_options']) ? $_POST['product_options'] : []; if (is_string($raw_options)) { $decoded_opts = json_decode(stripslashes($raw_options), true); if (is_array($decoded_opts)) { $raw_options = $decoded_opts; } } if (!empty($raw_options)) { $product_attributes = get_post_meta($product_id, '_product_attributes', true); if (!is_array($product_attributes)) { $product_attributes = []; } $position = 0; foreach ($raw_options as $option) { if (empty($option['name']) || empty($option['values'])) continue; $opt_name = sanitize_text_field($option['name']); $opt_values_raw = is_array($option['values']) ? $option['values'] : explode('|', (string)$option['values']); $opt_values = array_filter(array_map('sanitize_text_field', array_map('trim', $opt_values_raw))); $attr_key = sanitize_title($opt_name); $tax_name = str_starts_with($attr_key, 'pa_') ? $attr_key : 'pa_' . $attr_key; $is_taxonomy = taxonomy_exists($tax_name) || taxonomy_exists($opt_name); // If it is a registered taxonomy, sync terms on parent product if ($is_taxonomy && taxonomy_exists($tax_name)) { $term_ids = []; foreach ($opt_values as $val_item) { $term = get_term_by('name', $val_item, $tax_name); if (!$term || is_wp_error($term)) { $term = get_term_by('slug', sanitize_title($val_item), $tax_name); } if (!$term || is_wp_error($term)) { $inserted = wp_insert_term($val_item, $tax_name); if (!is_wp_error($inserted) && isset($inserted['term_id'])) { $term_ids[] = (int)$inserted['term_id']; } } else { $term_ids[] = (int)$term->term_id; } } if (!empty($term_ids)) { wp_set_object_terms($product_id, $term_ids, $tax_name); } } $product_attributes[$attr_key] = [ 'name' => $opt_name, 'value' => implode(' | ', $opt_values), 'position' => $position, 'is_visible' => 1, 'is_variation' => 1, 'is_taxonomy' => $is_taxonomy ? 1 : 0, ]; $position++; } update_post_meta($product_id, '_product_attributes', $product_attributes); if (!empty($product_attributes) && class_exists('WC_Product_Attribute') && class_exists('WC_Product_Variable')) { $wc_attributes = []; foreach ($product_attributes as $attr_key => $attr_data) { $attribute = new \WC_Product_Attribute(); $attribute->set_name($attr_data['name']); $attribute->set_options(explode(' | ', $attr_data['value'])); $attribute->set_position($attr_data['position']); $attribute->set_visible(true); $attribute->set_variation(true); $wc_attributes[] = $attribute; } $wc_var_prod = new \WC_Product_Variable($product_id); $wc_var_prod->set_attributes($wc_attributes); $wc_var_prod->save(); } } else { $product_attributes = get_post_meta($product_id, '_product_attributes', true); if (!is_array($product_attributes)) { $product_attributes = []; } } // Handle Variations $raw_variations = isset($_POST['variations']) ? $_POST['variations'] : []; if (is_string($raw_variations)) { $decoded_vars = json_decode(stripslashes($raw_variations), true); if (is_array($decoded_vars)) { $raw_variations = $decoded_vars; } } if (!empty($raw_variations)) { $existing_variations = get_posts([ 'post_parent' => $product_id, 'post_type' => 'product_variation', 'numberposts' => -1, 'fields' => 'ids', ]); $has_instock_variation = false; foreach ($raw_variations as $idx => $variation_data) { $v_id_sub = isset($variation_data['id']) ? intval($variation_data['id']) : 0; $var_price_raw = isset($variation_data['regular_price']) && $variation_data['regular_price'] !== '' ? $variation_data['regular_price'] : (isset($variation_data['price']) ? $variation_data['price'] : ''); $var_price = ($var_price_raw !== '') ? wc_format_decimal($var_price_raw) : 0; $var_sale_price_raw = isset($variation_data['sale_price']) && $variation_data['sale_price'] !== '' ? $variation_data['sale_price'] : ''; $var_sale_price = ($var_sale_price_raw !== '') ? wc_format_decimal($var_sale_price_raw) : ''; $var_stock_raw = isset($variation_data['stock']) && $variation_data['stock'] !== '' ? $variation_data['stock'] : ''; $var_stock = ($var_stock_raw !== '') ? wc_stock_amount($var_stock_raw) : ''; $var_stock_status = isset($variation_data['stock_status']) ? sanitize_text_field($variation_data['stock_status']) : 'instock'; $var_image_id = isset($variation_data['image_id']) ? intval($variation_data['image_id']) : 0; // Parse submitted attribute key-value map for duplicate check & storage $submitted_attributes = []; $var_attrs_input = isset($variation_data['attributes']) ? $variation_data['attributes'] : ''; if (is_string($var_attrs_input)) { $decoded_attrs = json_decode(stripslashes($var_attrs_input), true); if (is_array($decoded_attrs)) { $var_attrs_input = $decoded_attrs; } } if (is_string($var_attrs_input)) { $var_attrs_arr = explode('|', $var_attrs_input); $attr_index = 0; foreach ($product_attributes as $key => $attr_meta) { $term = isset($var_attrs_arr[$attr_index]) ? sanitize_text_field(trim($var_attrs_arr[$attr_index])) : ''; if ($term !== '') { $taxonomy_key = taxonomy_exists('pa_' . $key) ? 'attribute_pa_' . $key : 'attribute_' . $key; $submitted_attributes[$taxonomy_key] = $term; } $attr_index++; } } elseif (is_array($var_attrs_input)) { foreach ($var_attrs_input as $attr_k => $attr_v) { $base_k = str_replace(['attribute_', 'pa_'], '', $attr_k); $base_k_slug = sanitize_title($base_k); if (str_starts_with($attr_k, 'attribute_')) { $clean_k = $attr_k; } elseif (taxonomy_exists('pa_' . $base_k_slug)) { $clean_k = 'attribute_pa_' . $base_k_slug; } else { $clean_k = 'attribute_' . $base_k_slug; } $submitted_attributes[$clean_k] = sanitize_text_field($attr_v); } } // Strict Parent Ownership Safety Check: ensure v_id_sub actually belongs to product_id $is_existing = false; if ($v_id_sub > 0) { if (in_array($v_id_sub, $existing_variations, true)) { $is_existing = true; } else { // Re-verify post parent in DB to prevent cross-merchant manipulation $var_post = get_post($v_id_sub); if ($var_post && $var_post->post_type === 'product_variation' && (int)$var_post->post_parent === (int)$product_id) { $is_existing = true; } else { // Invalid variation ID for this parent: treat as new variation $v_id_sub = 0; } } } // Duplicate Combination Prevention: Match against existing variations if adding new if (!$is_existing && !empty($submitted_attributes)) { foreach ($existing_variations as $ex_v_id) { $ex_v_obj = wc_get_product($ex_v_id); if (!$ex_v_obj) continue; $ex_attrs = $ex_v_obj->get_attributes(); $is_duplicate = true; foreach ($submitted_attributes as $s_key => $s_val) { $check_val = isset($ex_attrs[$s_key]) ? $ex_attrs[$s_key] : get_post_meta($ex_v_id, $s_key, true); if (sanitize_title($check_val) !== sanitize_title($s_val)) { $is_duplicate = false; break; } } if ($is_duplicate) { $v_id_sub = $ex_v_id; $is_existing = true; break; } } } // Handle Deletion for verified existing variation if (!empty($variation_data['delete'])) { if ($is_existing && $v_id_sub > 0) { wp_delete_post($v_id_sub, true); } continue; } if ($is_existing && $v_id_sub > 0) { $variation_id = $v_id_sub; wp_update_post([ 'ID' => $variation_id, 'post_title' => sprintf('Variation #%d of %s', $idx + 1, !empty($title) ? $title : 'Product'), 'menu_order' => $idx, ]); } else { $variation_id = wp_insert_post([ 'post_title' => sprintf('Variation #%d of %s', $idx + 1, !empty($title) ? $title : 'Product'), 'post_status' => 'publish', 'post_parent' => $product_id, 'post_type' => 'product_variation', 'menu_order' => $idx, ]); if ($variation_id && !is_wp_error($variation_id)) { $existing_variations[] = $variation_id; } } if ($variation_id && !is_wp_error($variation_id)) { $var_obj = wc_get_product($variation_id); if (!$var_obj || !$var_obj->is_type('variation')) { $var_obj = new WC_Product_Variation($variation_id); } $var_obj->set_regular_price($var_price); update_post_meta($variation_id, '_regular_price', $var_price); // Strict Sale Price Validation: Sale price MUST be > 0 and < Regular Price if ($var_sale_price !== '' && floatval($var_sale_price) > 0 && floatval($var_sale_price) < floatval($var_price)) { $var_obj->set_sale_price(floatval($var_sale_price)); $var_obj->set_price(floatval($var_sale_price)); update_post_meta($variation_id, '_sale_price', floatval($var_sale_price)); update_post_meta($variation_id, '_price', floatval($var_sale_price)); } else { $var_obj->set_sale_price(''); $var_obj->set_price($var_price); delete_post_meta($variation_id, '_sale_price'); update_post_meta($variation_id, '_price', $var_price); } if ($var_stock !== '') { $var_obj->set_manage_stock(true); $var_obj->set_stock_quantity($var_stock); $calc_status = $var_stock > 0 ? (!empty($var_stock_status) ? $var_stock_status : 'instock') : 'outofstock'; $var_obj->set_stock_status($calc_status); update_post_meta($variation_id, '_manage_stock', 'yes'); update_post_meta($variation_id, '_stock', $var_stock); update_post_meta($variation_id, '_stock_status', $calc_status); if ($var_stock > 0 || $calc_status === 'onbackorder') { $has_instock_variation = true; } } else { $var_obj->set_manage_stock(false); $var_obj->set_stock_status($var_stock_status); update_post_meta($variation_id, '_manage_stock', 'no'); update_post_meta($variation_id, '_stock_status', $var_stock_status); delete_post_meta($variation_id, '_stock'); if ($var_stock_status === 'instock' || $var_stock_status === 'onbackorder') { $has_instock_variation = true; } } if ($var_image_id > 0) { $var_obj->set_image_id($var_image_id); set_post_thumbnail($variation_id, $var_image_id); } else if ($var_image_id === 0) { delete_post_thumbnail($variation_id); } // Preserve & update variation attributes safely $current_wc_attrs = $var_obj->get_attributes(); $final_attributes = []; if (!empty($submitted_attributes)) { foreach ($submitted_attributes as $s_meta_key => $s_meta_val) { $raw_key = str_starts_with($s_meta_key, 'attribute_') ? substr($s_meta_key, 10) : $s_meta_key; $tax_name = str_starts_with($raw_key, 'pa_') ? $raw_key : 'pa_' . $raw_key; $clean_val = $s_meta_val; if (taxonomy_exists($tax_name)) { $term = get_term_by('name', $s_meta_val, $tax_name); if (!$term || is_wp_error($term)) { $term = get_term_by('slug', sanitize_title($s_meta_val), $tax_name); } if ($term && !is_wp_error($term)) { $clean_val = $term->slug; } else { $clean_val = sanitize_title($s_meta_val); } } $final_attributes[$s_meta_key] = $clean_val; update_post_meta($variation_id, $s_meta_key, $clean_val); if (str_starts_with($s_meta_key, 'attribute_pa_')) { update_post_meta($variation_id, 'attribute_' . str_replace('attribute_', '', $s_meta_key), $clean_val); } } } // If updating existing variation and no valid new attributes supplied, preserve existing attributes if (empty($final_attributes) && !empty($current_wc_attrs)) { foreach ($current_wc_attrs as $c_key => $c_val) { $meta_k = str_starts_with($c_key, 'attribute_') ? $c_key : 'attribute_' . $c_key; $final_attributes[$meta_k] = $c_val; update_post_meta($variation_id, $meta_k, $c_val); } } if (!empty($final_attributes)) { $var_obj->set_attributes($final_attributes); } $var_obj->save(); } } if ($manage_stock !== 'yes') { update_post_meta($product_id, '_stock_status', $has_instock_variation ? 'instock' : 'outofstock'); } else { update_post_meta($product_id, '_stock_status', $stock_qty > 0 ? $stock_status : 'outofstock'); } if (class_exists('WC_Product_Variable')) { \WC_Product_Variable::sync($product_id); \WC_Product_Variable::sync_stock_status($product_id); } } } delete_transient('wc_product_children_' . $product_id); delete_transient('wc_var_prices_' . $product_id); wc_delete_product_transients($product_id); clean_post_cache($product_id); $updated_post = get_post($product_id); wp_send_json_success([ 'product_id' => $product_id, 'message' => 'Product successfully updated.', 'title' => $updated_post->post_title, 'slug' => $updated_post->post_name, 'permalink' => get_permalink($product_id), 'status' => ucfirst($updated_post->post_status), ]); } /** * Update order delivery handler meta via AJAX (_dezia_delivery_handler: 'dezia' | 'merchant'). */ public function dezia_update_order_delivery_handler() { if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Unauthorized'], 401); } $order_id = isset($_POST['order_id']) ? absint($_POST['order_id']) : 0; $handler = isset($_POST['handler']) ? sanitize_text_field($_POST['handler']) : 'dezia'; if ($order_id <= 0) { wp_send_json_error(['message' => 'Invalid order ID'], 400); } if (!in_array($handler, ['dezia', 'merchant'], true)) { $handler = 'dezia'; } update_post_meta($order_id, '_dezia_delivery_handler', $handler); // If updated to 'dezia' and status is already packaged, trigger dispatch alert $order = function_exists('wc_get_order') ? wc_get_order($order_id) : null; if ($order) { $status = str_replace('wc-', '', strtolower($order->get_status())); if ($status === 'packaged' && $handler === 'dezia') { $notifier = new \Dezia\Core\Service\NotificationManager(); $notifier->sendOperationsDispatchAlert($order_id); } } wp_send_json_success([ 'message' => 'Delivery handler updated successfully.', 'order_id' => $order_id, 'handler' => $handler, ]); } /** * Mark order as packaged via AJAX and trigger logistics notification workflow if handler is 'dezia'. */ public function dezia_mark_order_packaged() { if (!is_user_logged_in()) { wp_send_json_error(['message' => 'Unauthorized'], 401); } $order_id = isset($_POST['order_id']) ? absint($_POST['order_id']) : 0; if ($order_id <= 0) { wp_send_json_error(['message' => 'Invalid order ID'], 400); } if (!function_exists('wc_get_order')) { wp_send_json_error(['message' => 'WooCommerce is not active'], 500); } $order = wc_get_order($order_id); if (!$order) { wp_send_json_error(['message' => 'Order not found'], 404); } // Verify that user is author of at least one item in the order $user_id = get_current_user_id(); $owns_any = false; foreach ($order->get_items() as $item) { $product = $item->get_product(); $post = $product ? $product->get_post_data() : null; if ($post && (int) $post->post_author === (int) $user_id) { $owns_any = true; break; } } if (!$owns_any && !current_user_can('manage_options')) { wp_send_json_error(['message' => 'You do not have permission to update this order'], 403); } // Update status to packaged $order->update_status('packaged', 'Order marked as packaged via SManager Dashboard.'); // Trigger dispatch alert if delivery handler is 'dezia' $handler = get_post_meta($order_id, '_dezia_delivery_handler', true) ?: 'dezia'; if ($handler === 'dezia' && class_exists('\Dezia\Core\Service\NotificationManager')) { $notifier = new \Dezia\Core\Service\NotificationManager(); $notifier->sendOperationsDispatchAlert($order_id); } wp_send_json_success([ 'message' => 'Order marked as packaged successfully.', 'order_id' => $order_id, 'status' => 'packaged', ]); }}