%1$s %2$s
',
esc_html__('3DM Connect', '3dm-connect'),
esc_html__('requires WooCommerce to be installed and active.', '3dm-connect')
);
}
add_action('admin_enqueue_scripts', 'connect_3dm_enqueue_assets');
function connect_3dm_enqueue_assets($hook) {
if (strpos($hook, 'woocommerce_page_wc-settings') === false) return;
wp_enqueue_style('c3dm-style', plugin_dir_url(__FILE__) . 'style.css', array(), '6.2.8');
wp_enqueue_script('c3dm-script', plugin_dir_url(__FILE__) . 'script.js', array('jquery'), '6.2.8', true);
$definitions = connect_3dm_get_definitions();
$saved_data = array();
foreach($definitions as $key => $def) {
$saved_data[$key] = array(
'tpl' => get_option("connect_3dm_{$key}_template"),
'map' => get_option("connect_3dm_{$key}_map", array())
);
}
wp_localize_script('c3dm-script', 'c3dm_params', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('c3dm_nonce_action'),
'templates' => get_option('connect_3dm_cached_templates', array()),
'events' => $definitions,
'saved' => $saved_data,
'i18n' => array(
'syncing' => esc_html__('Syncing...', '3dm-connect'),
'refresh' => esc_html__('🔄 Refresh Templates', '3dm-connect'),
'error' => esc_html__('Error: ', '3dm-connect'),
'ajax_fail' => esc_html__('Server connection failed. Please try again.', '3dm-connect'),
'invalid_template' => esc_html__("⚠️ Cannot Select Template\n\nThe template '{template}' uses a {type} header.\n\nThis version of the plugin only supports TEXT headers. Please choose a different template.", '3dm-connect'),
'skip' => esc_html__('-- Skip --', '3dm-connect'),
'no_vars' => esc_html__('No variables found in this template.', '3dm-connect'),
'select_tpl' => esc_html__('Select a template to configure mapping.', '3dm-connect')
)
));
}
function connect_3dm_get_definitions() {
$vars_order = array(
'billing_first_name' => __('First Name', '3dm-connect'), 'billing_last_name' => __('Last Name', '3dm-connect'), 'billing_phone' => __('Phone', '3dm-connect'),
'order_id' => __('Order ID', '3dm-connect'), 'order_total' => __('Total Amount', '3dm-connect'), 'currency' => __('Currency', '3dm-connect'),
'payment_method' => __('Payment Mode', '3dm-connect'), 'order_status' => __('Status', '3dm-connect'), 'order_date' => __('Date', '3dm-connect'),
'item_count' => __('Items', '3dm-connect'), 'customer_note' => __('Note', '3dm-connect'), 'store_name' => __('Store Name', '3dm-connect'), 'order_view_url' => __('Order Link', '3dm-connect')
);
$vars_refund = array_merge($vars_order, array('refund_amount' => __('Refund Amount', '3dm-connect'), 'refund_reason' => __('Reason', '3dm-connect')));
$vars_cart = array(
'billing_first_name' => __('First Name', '3dm-connect'),
'billing_phone' => __('Phone', '3dm-connect'),
'cart_total' => __('Cart Total', '3dm-connect'),
'checkout_url' => __('Checkout URL', '3dm-connect')
);
return array(
'new_order' => array('label' => __('New Order', '3dm-connect'), 'icon' => '📦', 'hook' => 'woocommerce_new_order', 'vars' => $vars_order),
'completed' => array('label' => __('Completed', '3dm-connect'), 'icon' => '✅', 'hook' => 'woocommerce_order_status_completed', 'vars' => $vars_order),
'on_hold' => array('label' => __('On-Hold', '3dm-connect'), 'icon' => '⏳', 'hook' => 'woocommerce_order_status_on-hold', 'vars' => $vars_order),
'cancelled' => array('label' => __('Cancelled', '3dm-connect'), 'icon' => '❌', 'hook' => 'woocommerce_order_status_cancelled', 'vars' => $vars_order),
'failed' => array('label' => __('Failed', '3dm-connect'), 'icon' => '⚠️', 'hook' => 'woocommerce_order_status_failed', 'vars' => $vars_order),
'refunded' => array('label' => __('Refunded', '3dm-connect'), 'icon' => '💰', 'hook' => 'woocommerce_order_refunded', 'vars' => $vars_refund),
'pending' => array('label' => __('Pending Payment', '3dm-connect'), 'icon' => '🕒', 'hook' => 'woocommerce_order_status_pending', 'vars' => $vars_order),
'abandoned_cart' => array('label' => __('Abandoned Cart', '3dm-connect'), 'icon' => '🛒', 'hook' => 'connect_3dm_cart_abandoned_trigger', 'vars' => $vars_cart)
);
}
function connect_3dm_settings_page() {
if (!current_user_can('manage_woocommerce')) {
return;
}
$company_hash = get_option('connect_3dm_company_hash', '');
$definitions = connect_3dm_get_definitions();
$active_events = get_option('connect_3dm_active_events', array());
?>
$def) {
$template = isset($_POST["connect_3dm_{$key}_template"]) ? sanitize_text_field(wp_unslash($_POST["connect_3dm_{$key}_template"])) : '';
update_option("connect_3dm_{$key}_template", $template);
if ( isset( $_POST["connect_3dm_{$key}_map"] ) && is_array( $_POST["connect_3dm_{$key}_map"] ) ) {
$sanitized_map = map_deep( wp_unslash( $_POST["connect_3dm_{$key}_map"] ), 'sanitize_text_field' );
update_option( "connect_3dm_{$key}_map", $sanitized_map );
}
}
}
// --- 5. AJAX HANDLER ---
add_action('wp_ajax_connect_3dm_refresh_templates', 'connect_3dm_ajax_refresh_templates');
function connect_3dm_ajax_refresh_templates() {
check_ajax_referer('c3dm_nonce_action', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(__('Unauthorized access.', '3dm-connect'));
}
$hash = isset($_POST['hash']) ? sanitize_text_field(wp_unslash($_POST['hash'])) : '';
if (empty($hash)) {
wp_send_json_error(__('Company Hash is required.', '3dm-connect'));
}
update_option('connect_3dm_company_hash', $hash);
$response = wp_remote_post('https://connect.3dm-sols.com/api/get-all-templates.php', array(
'headers' => array('Authorization' => 'Bearer ' . $hash),
'timeout' => 20
));
if (is_wp_error($response)) {
wp_send_json_error(__('API Request Failed: ', '3dm-connect') . $response->get_error_message());
}
$data = json_decode(wp_remote_retrieve_body($response), true);
if (isset($data['templates']) && is_array($data['templates'])) {
update_option('connect_3dm_cached_templates', $data['templates']);
wp_send_json_success();
} else {
wp_send_json_error(__('Failed to retrieve templates. Check your Hash.', '3dm-connect'));
}
}
function connect_3dm_log($message) {
if (function_exists('wc_get_logger')) {
$logger = wc_get_logger();
$context = array('source' => '3dm-connect');
$logger->info($message, $context);
}
}
// --- 6. EVENT TRIGGERS (Async via Action Scheduler) ---
function connect_3dm_register_event_hooks() {
$active = get_option('connect_3dm_active_events', array());
if (empty($active) || !is_array($active)) return;
foreach(connect_3dm_get_definitions() as $key => $def) {
if ($key === 'abandoned_cart') continue;
if(in_array($key, $active)) {
add_action($def['hook'], 'connect_3dm_dispatch_event', 10, 2);
}
}
}
function connect_3dm_dispatch_event($arg1 = null, $arg2 = null) {
$current_hook = current_action();
$event_key = '';
foreach(connect_3dm_get_definitions() as $key => $def) {
if ($def['hook'] === $current_hook) {
$event_key = $key;
break;
}
}
if (empty($event_key)) return;
$order_id = is_numeric($arg1) ? $arg1 : (is_a($arg1, 'WC_Order') ? $arg1->get_id() : 0);
if (!$order_id) return;
$refund_id = ($event_key === 'refunded' && $arg2) ? $arg2 : 0;
if (function_exists('as_enqueue_async_action')) {
as_enqueue_async_action('connect_3dm_process_api_call', array($event_key, $order_id, $refund_id), '3dm-connect');
} else {
connect_3dm_process_api_call($event_key, $order_id, $refund_id);
}
}
add_action('connect_3dm_process_api_call', 'connect_3dm_process_api_call', 10, 3);
function connect_3dm_process_api_call($key, $order_id, $refund_id) {
$tpl = get_option("connect_3dm_{$key}_template");
if(empty($tpl)) return;
$order = wc_get_order($order_id);
if(!$order || !is_a($order, 'WC_Order')) return;
connect_3dm_log("EVENT TRIGGERED (ASYNC): {$key} for Order ID: " . $order->get_id());
$extra = array();
if($key === 'refunded' && $refund_id) {
$refund = wc_get_order($refund_id);
if ($refund && is_a($refund, 'WC_Order_Refund')) {
$extra = array('refund_amount' => $refund->get_amount(), 'refund_reason' => $refund->get_reason());
}
}
$map = get_option("connect_3dm_{$key}_map");
$hash = get_option('connect_3dm_company_hash');
$templates = get_option('connect_3dm_cached_templates', array());
$tpl_data = null;
foreach($templates as $t) {
if($t['name'] == $tpl) {
$tpl_data = $t;
break;
}
}
if(!$tpl_data || empty($tpl_data['body'])) return;
preg_match_all('/\{\{(.*?)\}\}/', $tpl_data['body'], $matches);
$v_names = $matches[1];
$body_params = array();
if(isset($map['body']) && is_array($map['body'])) {
foreach($map['body'] as $idx => $m_key) {
$val = connect_3dm_get_val($order, $m_key);
if(empty($val) && isset($extra[$m_key])) $val = $extra[$m_key];
$p = ['type' => 'text', 'text' => strval($val)];
if(isset($v_names[$idx]) && !is_numeric($v_names[$idx])) {
$p['parameter_name'] = $v_names[$idx];
}
$body_params[] = $p;
}
}
$response = wp_remote_post('https://connect.3dm-sols.com/api/send-template.php', array(
'method' => 'POST',
'headers' => ['Content-Type'=>'application/json', 'Authorization'=>'Bearer '.$hash],
'body' => wp_json_encode(['phone_number' => $order->get_billing_phone(), 'template_name' => $tpl, 'body_parameters' => $body_params])
));
if (is_wp_error($response)) {
connect_3dm_log("API ERROR: " . $response->get_error_message());
} else {
connect_3dm_log("API RESPONSE: " . wp_remote_retrieve_body($response));
}
}
function connect_3dm_get_val($order, $key) {
switch($key) {
case 'billing_first_name': return $order->get_billing_first_name();
case 'billing_last_name': return $order->get_billing_last_name();
case 'billing_phone': return $order->get_billing_phone();
case 'order_id': return $order->get_id();
case 'order_total': return $order->get_total();
case 'currency': return $order->get_currency();
case 'payment_method': return $order->get_payment_method_title();
case 'order_status': return $order->get_status();
case 'order_date': $date = $order->get_date_created(); return $date ? $date->date('Y/F/d') : '';
case 'item_count':
$count = 0;
$order_count = $order->get_item_count();
if ($order_count) {
$count = $order_count;
} else {
foreach($order->get_items() as $item) $count += $item->get_quantity();
}
if (!$count && isset(WC()->cart)) {
$count = WC()->cart->get_cart_contents_count();
}
return $count ? $count : 0;
case 'customer_note': return $order->get_customer_note();
case 'store_name': return get_bloginfo('name');
case 'order_view_url': return $order->get_view_order_url();
default: return '';
}
}
// --- 7. ABANDONED CART LOGIC ---
add_action( 'woocommerce_checkout_update_order_review', 'connect_3dm_capture_checkout_data' );
function connect_3dm_capture_checkout_data( $post_data ) {
$active_events = get_option('connect_3dm_active_events', array());
if ( ! in_array( 'abandoned_cart', $active_events ) ) return;
parse_str( $post_data, $data );
if ( empty( $data['billing_phone'] ) || empty( $data['billing_first_name'] ) ) return;
if ( ! WC()->cart || WC()->cart->is_empty() ) return;
$phone = sanitize_text_field( $data['billing_phone'] );
$name = sanitize_text_field( $data['billing_first_name'] );
$total = WC()->cart->get_total('');
$args = array(
'phone' => $phone,
'name' => $name,
'cart_total' => html_entity_decode(wp_strip_all_tags($total))
);
$group = '3dm-connect';
if ( function_exists('as_unschedule_all_actions') && function_exists('as_schedule_single_action') ) {
as_unschedule_all_actions( 'connect_3dm_process_abandoned_cart', array( $args ), $group );
as_schedule_single_action( time() + 3600, 'connect_3dm_process_abandoned_cart', array( $args ), $group );
}
}
add_action( 'connect_3dm_process_abandoned_cart', 'connect_3dm_execute_abandoned_cart', 10, 1 );
function connect_3dm_execute_abandoned_cart( $args ) {
$phone = $args['phone'];
$recent_orders = wc_get_orders( array(
'billing_phone' => $phone,
'date_created' => '>' . ( time() - 7200 ),
'limit' => 1,
'return' => 'ids',
) );
if ( ! empty( $recent_orders ) ) {
connect_3dm_log("Abandoned cart aborted: Order found for phone {$phone}");
return;
}
$tpl = get_option("connect_3dm_abandoned_cart_template");
if ( empty($tpl) ) return;
$map = get_option("connect_3dm_abandoned_cart_map");
$hash = get_option('connect_3dm_company_hash');
$templates = get_option('connect_3dm_cached_templates', array());
$tpl_data = null;
foreach($templates as $t) {
if($t['name'] == $tpl) { $tpl_data = $t; break; }
}
if(!$tpl_data || empty($tpl_data['body'])) return;
preg_match_all('/\{\{(.*?)\}\}/', $tpl_data['body'], $matches);
$v_names = $matches[1];
$body_params = array();
$checkout_url = wc_get_checkout_url();
if(isset($map['body']) && is_array($map['body'])) {
foreach($map['body'] as $idx => $m_key) {
$val = '';
switch($m_key) {
case 'billing_first_name': $val = $args['name']; break;
case 'billing_phone': $val = $args['phone']; break;
case 'cart_total': $val = $args['cart_total']; break;
case 'checkout_url': $val = $checkout_url; break;
}
$p = ['type' => 'text', 'text' => strval($val)];
if(isset($v_names[$idx]) && !is_numeric($v_names[$idx])) {
$p['parameter_name'] = $v_names[$idx];
}
$body_params[] = $p;
}
}
$response = wp_remote_post('https://connect.3dm-sols.com/api/send-template.php', array(
'method' => 'POST',
'headers' => ['Content-Type'=>'application/json', 'Authorization'=>'Bearer '.$hash],
'body' => wp_json_encode(['phone_number' => $phone, 'template_name' => $tpl, 'body_parameters' => $body_params])
));
if (is_wp_error($response)) {
connect_3dm_log("ABANDONED CART API ERROR: " . $response->get_error_message());
} else {
connect_3dm_log("ABANDONED CART API RESPONSE: " . wp_remote_retrieve_body($response));
}
}
?>