'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') : '
';
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',
]);
}}