strtoupper($method), 'timeout' => 30, 'headers' => ['Content-Type' => 'application/json'] ]; if ($payload && in_array($method, ['POST', 'PATCH', 'PUT'])) { $args['body'] = wp_json_encode($payload); } $response = wp_remote_request($url, $args); // ✅ ERROR HANDLING if (is_wp_error($response)) { wpfd_log("HTTP Error: " . $response->get_error_message()); return false; } return $response; } } // ===================== // ✅ FIXED: PHONE NORMALIZATION (HANDLES EMPTY) // ===================== if (!function_exists('wpfd_normalize_phone')) { function wpfd_normalize_phone($phone) { $digits_only = preg_replace('/\D/', '', (string) $phone); // ✅ FIX: Return empty string for empty input if ($digits_only === '') { return ''; } // Handle Moroccan numbers if (preg_match('/^212(\d{9})$/', $digits_only, $matches)) { return '+212' . $matches[1]; } // Handle local format (0XXXXXXXXX) if (preg_match('/^0(\d{9})$/', $digits_only, $matches)) { return '+212' . $matches[1]; } // Return with + prefix return '+' . $digits_only; } } // ===================== // PERSON MANAGEMENT WITH ERROR HANDLING // ===================== if (!function_exists('wpfd_find_person_by_phone')) { function wpfd_find_person_by_phone($phone) { $normalized_phone = wpfd_normalize_phone($phone); if (empty($normalized_phone)) { return false; } // Try exact match $url = wpfd_pd_url('persons/search') . '&term=' . urlencode($normalized_phone) . '&fields=phone&exact_match=1'; $response = wp_remote_get($url); if (is_wp_error($response)) { wpfd_log("Error searching person: " . $response->get_error_message()); return false; } $data = json_decode(wp_remote_retrieve_body($response), true); if (!empty($data['data']['items'][0]['item']['id'])) { $person_id = (int)$data['data']['items'][0]['item']['id']; wpfd_log("Person found: {$person_id} for {$normalized_phone}"); return $person_id; } return false; } } if (!function_exists('wpfd_create_or_find_person')) { function wpfd_create_or_find_person($name, $email, $phone) { // First try to find existing person if (!empty($phone)) { $existing_person = wpfd_find_person_by_phone($phone); if ($existing_person) { return $existing_person; } } // Create new person $normalized_phone = wpfd_normalize_phone($phone); $payload = [ 'name' => sanitize_text_field($name), 'email' => $email ? [['value' => sanitize_email($email), 'primary' => true]] : [], 'phone' => $normalized_phone ? [['value' => $normalized_phone, 'primary' => true]] : [], ]; $response = wpfd_pd_request('POST', 'persons', $payload); if (!$response) { return 0; } $code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($code == 201 || $code == 200) { $data = json_decode($body, true); $person_id = (int)($data['data']['id'] ?? 0); wpfd_log("Created person: {$person_id}"); return $person_id; } wpfd_log("Create person failed. HTTP: {$code}"); return 0; } } // ===================== // ✅ ENHANCED: LEAD MANAGEMENT WITH DEDUPLICATION // ===================== if (!function_exists('wpfd_find_leads_by_person')) { function wpfd_find_leads_by_person($person_id, $title_prefix = '') { $response = wpfd_pd_request('GET', 'leads?person_id=' . intval($person_id)); if (!$response) { return []; } $data = json_decode(wp_remote_retrieve_body($response), true); $leads = []; if (!empty($data['data'])) { foreach ($data['data'] as $lead) { if (empty($title_prefix) || strpos($lead['title'], $title_prefix) === 0) { $leads[] = $lead['id']; } } } return $leads; } } if (!function_exists('wpfd_delete_lead')) { function wpfd_delete_lead($lead_id) { $response = wpfd_pd_request('DELETE', "leads/{$lead_id}"); if (!$response) { return false; } $code = wp_remote_retrieve_response_code($response); if ($code == 200) { wpfd_log("✅ Deleted lead: {$lead_id}"); return true; } wpfd_log("❌ Failed to delete lead {$lead_id}. HTTP: {$code}"); return false; } } if (!function_exists('wpfd_create_lead_with_note')) { function wpfd_create_lead_with_note($person_id, $title, $note_content) { $lead_payload = [ 'title' => sanitize_text_field($title), 'person_id' => (int)$person_id, 'value' => ['amount' => 0, 'currency' => 'ILS'], WPFD_PD_SOURCE_CHANNEL_FIELD_KEY => (int) WPFD_PD_SOURCE_CHANNEL_OPTION_ID ]; $response = wpfd_pd_request('POST', 'leads', $lead_payload); if (!$response) { return null; } $code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($code == 201 || $code == 200) { $data = json_decode($body, true); $lead_id = $data['data']['id'] ?? null; if ($lead_id && $note_content) { wpfd_add_note_to_lead($lead_id, $note_content); } return $lead_id; } wpfd_log("Create lead failed. HTTP: {$code}"); return null; } } // ===================== // NOTE MANAGEMENT WITH ERROR HANDLING // ===================== if (!function_exists('wpfd_add_note_to_person')) { function wpfd_add_note_to_person($person_id, $content) { $response = wpfd_pd_request('POST', 'notes', [ 'person_id' => (int)$person_id, 'content' => $content ]); if (!$response) return false; $code = wp_remote_retrieve_response_code($response); return ($code == 201 || $code == 200); } } if (!function_exists('wpfd_add_note_to_lead')) { function wpfd_add_note_to_lead($lead_id, $content) { $response = wpfd_pd_request('POST', 'notes', [ 'lead_id' => $lead_id, 'content' => $content ]); if (!$response) return false; $code = wp_remote_retrieve_response_code($response); return ($code == 201 || $code == 200); } } if (!function_exists('wpfd_add_note_to_deal')) { function wpfd_add_note_to_deal($deal_id, $content) { $response = wpfd_pd_request('POST', 'notes', [ 'deal_id' => $deal_id, 'content' => $content ]); if (!$response) return false; $code = wp_remote_retrieve_response_code($response); return ($code == 201 || $code == 200); } } if (!function_exists('wpfd_find_lead_by_person')) { function wpfd_find_lead_by_person($person_id) { $response = wpfd_pd_request('GET', 'leads?person_id=' . intval($person_id)); if (!$response) return false; $data = json_decode(wp_remote_retrieve_body($response), true); return !empty($data['data'][0]['id']) ? $data['data'][0]['id'] : false; } } if (!function_exists('wpfd_find_deal_by_person')) { function wpfd_find_deal_by_person($person_id) { $response = wpfd_pd_request('GET', 'deals?person_id=' . intval($person_id)); if (!$response) return false; $data = json_decode(wp_remote_retrieve_body($response), true); return !empty($data['data'][0]['id']) ? $data['data'][0]['id'] : false; } } // ===================== // ✅ SECURE URL SHORTENER WITH HTTPS // ===================== if (!function_exists('wpfd_shortener_table_exists')) { function wpfd_shortener_table_exists() { global $wpdb; $table_name = $wpdb->prefix . 'wpfd_short_links'; return $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") === $table_name; } } if (!function_exists('wpfd_create_shortener_table')) { function wpfd_create_shortener_table() { global $wpdb; $table_name = $wpdb->prefix . 'wpfd_short_links'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, hash varchar(8) NOT NULL, full_url text NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, clicks int DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY hash (hash) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); wpfd_log("Shortener table created"); } } if (!function_exists('wpfd_create_short_link')) { function wpfd_create_short_link($full_url) { // ✅ CHECK: Ensure table exists if (!wpfd_shortener_table_exists()) { wpfd_log("Shortener table missing - creating now"); wpfd_create_shortener_table(); } global $wpdb; $table_name = $wpdb->prefix . 'wpfd_short_links'; // Check if URL already shortened $existing = $wpdb->get_var($wpdb->prepare( "SELECT hash FROM $table_name WHERE full_url = %s", $full_url )); if ($existing) { return home_url("/est/{$existing}"); // ✅ HTTPS + Flexible domain } // Generate new hash $hash = substr(md5($full_url . microtime(true) . wp_rand()), 0, 8); // Store in database $wpdb->insert( $table_name, [ 'hash' => $hash, 'full_url' => $full_url ] ); $short_url = home_url("/est/{$hash}"); // ✅ HTTPS + Flexible domain wpfd_log("Created short link: {$short_url}"); return $short_url; } } // Redirect handler add_action('init', function() { $request_uri = $_SERVER['REQUEST_URI'] ?? ''; if (preg_match('#^/est/([a-z0-9]{8})/?$#i', $request_uri, $matches)) { $hash = $matches[1]; global $wpdb; $table_name = $wpdb->prefix . 'wpfd_short_links'; $full_url = $wpdb->get_var($wpdb->prepare( "SELECT full_url FROM $table_name WHERE hash = %s", $hash )); if ($full_url) { $wpdb->query($wpdb->prepare( "UPDATE $table_name SET clicks = clicks + 1 WHERE hash = %s", $hash )); wp_redirect($full_url, 302); exit; } else { wp_redirect(home_url('/'), 302); exit; } } }); // ===================== // ✅ SECURE ESTIMATION LINK WITH HTTPS // ===================== if (!function_exists('wpfd_generate_estimation_form_link')) { function wpfd_generate_estimation_form_link($name, $phone, $email, $entry_id) { // ✅ SECURE: Use home_url with HTTPS $base_domain = str_replace(['http://', 'https://'], '', home_url()); $estimation_url = "https://{$base_domain}/ar/medical-form/"; $name_parts = explode(' ', trim($name), 2); $fname = $name_parts[0] ?? ''; $lname = $name_parts[1] ?? ''; $params = [ 'entry_id' => intval($entry_id), 'email' => sanitize_email($email), 'phone' => sanitize_text_field($phone), 'fname' => sanitize_text_field($fname), 'lname' => sanitize_text_field($lname) ]; $params = array_filter($params); $full_estimation_link = add_query_arg($params, $estimation_url); // Create short link (will auto-create table if needed) $short_link = wpfd_create_short_link($full_estimation_link); return $short_link; } } // ===================== // E2PDF WITH ERROR HANDLING // ===================== if (!function_exists('wpfd_generate_dynamic_pdf_link')) { function wpfd_generate_dynamic_pdf_link($entry_id, $template_id = null) { if (!function_exists('do_shortcode')) { wpfd_log("do_shortcode function not available"); return false; } $template_id = $template_id ?: WPFD_E2PDF_TEMPLATE_ID; $shortcode = '[e2pdf-download id="' . intval($template_id) . '" dataset="' . intval($entry_id) . '" output="url"]'; $pdf_url = do_shortcode($shortcode); if (empty($pdf_url) || $pdf_url === $shortcode || strpos($pdf_url, 'http') !== 0) { wpfd_log("E2PDF failed for entry #{$entry_id}, template #{$template_id}"); return false; } return $pdf_url; } } // ===================== // ✅ ENHANCED DUPLICATE PREVENTION (24 HOUR WINDOW) // ===================== if (!function_exists('wpfd_get_person_fingerprint')) { function wpfd_get_person_fingerprint($name, $email, $phone) { $normalized_phone = wpfd_normalize_phone($phone); return md5(strtolower(trim($name)) . '|' . strtolower(trim($email)) . '|' . $normalized_phone); } } if (!function_exists('wpfd_is_duplicate_abandonment')) { function wpfd_is_duplicate_abandonment($name, $email, $phone) { $fingerprint = wpfd_get_person_fingerprint($name, $email, $phone); // ✅ EXTENDED: 24 hour window instead of 5 minutes $last_processed = get_transient("wpfd_abandon_fp_{$fingerprint}"); if ($last_processed) { wpfd_log("Duplicate abandonment blocked: {$fingerprint}"); return true; } // Mark as processed for 24 hours set_transient("wpfd_abandon_fp_{$fingerprint}", time(), 24 * HOUR_IN_SECONDS); return false; } } // ===================== // ABANDONMENT HANDLER (hooks into WPForms native abandonment action) // ===================== add_action('wpforms_process_form_abandonment', 'wpfd_handle_abandonment', 10, 3); function wpfd_handle_abandonment($fields, $entry, $form_data) { $form_id = isset($form_data['id']) ? absint($form_data['id']) : 0; if ($form_id !== WPFD_TARGET_FORM_ID) { return; } $name = $fields[WPFD_FID_NAME]['value'] ?? ''; $email = $fields[WPFD_FID_EMAIL]['value'] ?? ''; $phone = $fields[WPFD_FID_PHONE]['value'] ?? ''; if (empty($name)) { wpfd_log('Abandoned form skipped - no name'); return; } // Prevent duplicates if (wpfd_is_duplicate_abandonment($name, $email, $phone)) { return; } wpfd_log("========== ABANDONED (PRODUCTION) =========="); $person_id = wpfd_create_or_find_person($name, $email, $phone); if (!$person_id) { return; } $note_content = "ABANDONED FORM\n\n"; $note_content .= "Patient: {$name}\n"; $note_content .= "Email: {$email}\n"; $note_content .= "Phone: {$phone}\n"; $note_content .= "Status: Partially completed"; $lead_id = wpfd_create_lead_with_note($person_id, 'Abandoned: ' . $name, $note_content); wpfd_log("Abandoned lead created: {$lead_id}"); wpfd_log("========== ABANDONED DONE =========="); } // ===================== // ✅ ENHANCED COMPLETION HANDLER // ===================== add_action('wpforms_process_complete', function ($fields, $entry, $form_data, $entry_id) { $form_id = isset($form_data['id']) ? absint($form_data['id']) : 0; // Handle Patient Intake Form if ($form_id === WPFD_TARGET_FORM_ID) { wpfd_log("========== COMPLETE (PRODUCTION) =========="); $name = $fields[WPFD_FID_NAME]['value'] ?? ''; $email = $fields[WPFD_FID_EMAIL]['value'] ?? ''; $phone = $fields[WPFD_FID_PHONE]['value'] ?? ''; if (empty($name)) return; $person_id = wpfd_create_or_find_person($name, $email, $phone); if (!$person_id) return; // ✅ ENHANCED: Delete ALL abandoned leads, check for existing complete leads $abandoned_leads = wpfd_find_leads_by_person($person_id, 'Abandoned:'); $complete_leads = wpfd_find_leads_by_person($person_id, 'Complete:'); // Clean up abandoned leads foreach ($abandoned_leads as $lead_id) { wpfd_delete_lead($lead_id); } // ✅ PREVENT DUPLICATE COMPLETE LEADS if (!empty($complete_leads)) { wpfd_log("Complete lead already exists for person {$person_id} - skipping"); return; } // Generate links $pdf_link = wpfd_generate_dynamic_pdf_link($entry_id); $estimation_link = wpfd_generate_estimation_form_link($name, $phone, $email, $entry_id); $note_content = "COMPLETE SUBMISSION\n\n"; $note_content .= "Patient: {$name}\n"; $note_content .= "Email: {$email}\n"; $note_content .= "Phone: {$phone}\n\n"; if ($pdf_link) { $note_content .= "📄 Patient PDF:\n{$pdf_link}\n\n"; } $note_content .= "📋 Doctor Estimation Form:\n{$estimation_link}\n\n"; $note_content .= "Instructions:\n"; $note_content .= "1. Review patient PDF\n"; $note_content .= "2. Complete estimation form\n"; $note_content .= "3. Submit assessment\n\n"; $note_content .= "Entry ID: #{$entry_id}"; $lead_id = wpfd_create_lead_with_note($person_id, 'Complete: ' . $name, $note_content); wpfd_log("Complete lead: {$lead_id}, Cleaned: " . count($abandoned_leads) . " abandoned"); wpfd_log("========== COMPLETE DONE =========="); } // Handle Doctor Estimation Form if ($form_id === WPFD_ESTIMATION_FORM_ID) { wpfd_log("========== DOCTOR ESTIMATION (PRODUCTION) =========="); $patient_phone = $fields[WPFD_FID_PATIENT_PHONE]['value'] ?? ''; $doctor_name = $fields[WPFD_FID_DOCTOR_NAME]['value'] ?? ''; $patient_phone = trim((string)$patient_phone); $doctor_name = trim((string)$doctor_name); $person_id = wpfd_find_person_by_phone($patient_phone); if (!$person_id) { wpfd_log("Cannot find person for phone: {$patient_phone}"); return; } $estimation_pdf = wpfd_generate_dynamic_pdf_link($entry_id, WPFD_ESTIMATION_E2PDF_TEMPLATE_ID); $note_content = "DOCTOR ESTIMATION\n\n"; $note_content .= "Doctor: {$doctor_name}\n"; $note_content .= "Date: " . date('Y-m-d H:i:s') . "\n\n"; if ($estimation_pdf) { $note_content .= "📄 Estimation PDF:\n{$estimation_pdf}\n\n"; } $note_content .= "Entry: #{$entry_id}"; // Add to Person, Lead, AND Deal wpfd_add_note_to_person($person_id, $note_content); $lead_id = wpfd_find_lead_by_person($person_id); if ($lead_id) { wpfd_add_note_to_lead($lead_id, $note_content); } $deal_id = wpfd_find_deal_by_person($person_id); if ($deal_id) { wpfd_add_note_to_deal($deal_id, $note_content); } wpfd_log("Estimation added - Person: {$person_id}, Lead: " . ($lead_id ?: 'N/A') . ", Deal: " . ($deal_id ?: 'N/A')); wpfd_log("========== DOCTOR ESTIMATION DONE =========="); } }, 10, 4); wpfd_log("WPForms-Pipedrive Production System Loaded"); /** * SETUP INSTRUCTIONS: * 1. Add to wp-config.php: define('PIPEDRIVE_API_TOKEN', 'your_new_token_here'); * 2. The shortener table will auto-create when first used * 3. Monitor: wp-content/wpfd-pipedrive.log */ https://claudecode.abdelportfolio.com/wp-sitemap-posts-post-1.xmlhttps://claudecode.abdelportfolio.com/wp-sitemap-posts-page-1.xmlhttps://claudecode.abdelportfolio.com/wp-sitemap-taxonomies-category-1.xmlhttps://claudecode.abdelportfolio.com/wp-sitemap-users-1.xml