Fix find trainer map: remove safari blocker, largely increase safety timeouts, update status
This commit is contained in:
parent
6d4bdc2f95
commit
f464224cd8
9 changed files with 1264 additions and 574 deletions
|
|
@ -2,44 +2,46 @@
|
|||
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -100 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i -E ''(fatal|error|warning|dashboard|manage)''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp plugin list | grep -E ''(events-calendar|tribe)''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp plugin deactivate the-events-calendar-community-events\")",
|
||||
"Bash(printf:*)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no benr@146.190.76.204 \"tail -100 /home/974670.cloudwaysapps.com/ncjzsayvsk/public_html/wp-content/debug.log | grep -i -E ''(security|nonce|edit|6288)''\")",
|
||||
"Bash(scripts/deploy.sh:*)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp post get 5346 --fields=ID,post_title,post_name\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -30 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i ''hvac announcements admin''\")",
|
||||
"mcp__playwright__browser_navigate",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user list --role=hvac_master_trainer --fields=user_login,user_email --format=table\")",
|
||||
"mcp__playwright__browser_type",
|
||||
"mcp__playwright__browser_click",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user update joe@upskillhvac.com --user_pass=''JoeTest123!'' --skip-email\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user list --role=hvac_trainer --fields=user_login,user_email --format=table | head -5\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -50 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i -E ''(master-trainer|trainers|javascript|enqueue)''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user get test_master --field=roles\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user create test_master_new test.master.new@example.com --role=hvac_master_trainer --user_pass=''TestNew123!'' --skip-email 2>&1 || wp user update test_master_new --user_pass=''TestNew123!'' --skip-email\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user list --field=user_login | grep test\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && which composer && composer --version\")",
|
||||
"Bash(unzip:*)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp plugin list | grep -E ''(wordpress-mcp|Name)''\")",
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:json.schemastore.org)",
|
||||
"WebFetch(domain:www.schemastore.org)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass:*)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e scp -o StrictHostKeyChecking=no /tmp/wordpress-mcp.zip roodev@146.190.76.204:/tmp/wordpress-mcp-prod.zip)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/ncjzsayvsk/public_html && wp plugin install /tmp/wordpress-mcp-prod.zip --activate\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no benr@146.190.76.204 \"ls -la /home/974670.cloudwaysapps.com/ | head -20\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cat /home/974670.cloudwaysapps.com/ncjzsayvsk/public_html/wp-config.php 2>&1 | grep -i ''site.*url'' | head -5 || echo ''Checking domain...''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y@1oVkz0M\" sshpass -e ssh -o StrictHostKeyChecking=no benr@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/ncjzsayvsk/public_html && wp plugin install /tmp/wordpress-mcp-prod.zip --activate\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y@1oVkz0M\" sshpass -e ssh -o StrictHostKeyChecking=no benr@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/ncjzsayvsk/public_html && wp plugin list | grep -E ''(wordpress-mcp|Name)''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user update master_trainer --user_pass=''MasterTest123!'' --skip-email 2>&1 || echo ''User does not exist, creating...'' && wp user create master_trainer master_trainer@example.com --role=hvac_master_trainer --user_pass=''MasterTest123!'' --skip-email\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp plugin list | grep -E ''(security|login|limit)''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp post list --post_type=page --s=''announcements'' --fields=ID,post_title,post_name,post_status --format=table\")",
|
||||
"mcp__zen__codereview",
|
||||
"mcp__playwright__browser_console_messages",
|
||||
"mcp__zen__debug",
|
||||
"mcp__playwright__browser_evaluate",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -200 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i ''enqueue''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -300 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i -E ''(wp_enqueue_scripts|init_hooks)''\")",
|
||||
"Bash(scripts/deploy.sh:*)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -100 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i ''HVAC_Announcements_Admin''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -150 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i ''HVAC_Announcements_Admin''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -200 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp cache flush\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -50 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i ''HVAC''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -200 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -A5 ''HVAC_Announcements_Admin.*enqueue_admin_assets called''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"tail -500 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log | grep -i ''enqueue.*admin.*assets\\|ENQUEUING SCRIPTS''\")",
|
||||
"mcp__zen__debug",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp user get 25 --field=roles\")",
|
||||
"WebSearch",
|
||||
"mcp__zen__chat",
|
||||
"mcp__playwright__browser_click",
|
||||
"mcp__playwright__browser_take_screenshot",
|
||||
"mcp__playwright__browser_wait_for"
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"grep -A5 ''wp_enqueue_script.*hvac-announcements-admin'' /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events/includes/class-hvac-announcements-admin.php | head -20\")",
|
||||
"mcp__zen__analyze",
|
||||
"mcp__playwright__browser_type",
|
||||
"mcp__playwright__browser_close",
|
||||
"mcp__playwright__browser_install",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp option get hvac_zoho_client_id 2>/dev/null || echo ''NOT SET''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp option get hvac_zoho_refresh_token 2>/dev/null || echo ''NOT SET''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp eval ''\nrequire_once ABSPATH . \"\"wp-content/plugins/hvac-community-events/includes/zoho/class-zoho-crm-auth.php\"\";\n$auth = new HVAC_Zoho_CRM_Auth();\n\necho \"\"=== ZOHO CRM READ-ONLY TEST ===\"\";\necho \"\"\\n\\n1. ORGANIZATION INFO:\\n\"\";\n$org = $auth->make_api_request(\"\"/org\"\", \"\"GET\"\");\nif (isset($org[\"\"org\"\"][0])) {\n $o = $org[\"\"org\"\"][0];\n echo \"\" Company: \"\" . $o[\"\"company_name\"\"] . \"\"\\n\"\";\n echo \"\" Country: \"\" . ($o[\"\"country\"\"] ?? \"\"N/A\"\") . \"\"\\n\"\";\n echo \"\" Time Zone: \"\" . ($o[\"\"time_zone\"\"] ?? \"\"N/A\"\") . \"\"\\n\"\";\n} else {\n echo \"\" Error: \"\" . print_r($org, true);\n}\n''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp eval ''\nrequire_once ABSPATH . \"\"wp-content/plugins/hvac-community-events/includes/zoho/class-zoho-crm-auth.php\"\";\n$auth = new HVAC_Zoho_CRM_Auth();\n\necho \"\"2. CONTACTS (first 5):\\n\"\";\n$contacts = $auth->make_api_request(\"\"/Contacts?per_page=5\"\", \"\"GET\"\");\nif (isset($contacts[\"\"data\"\"])) {\n foreach ($contacts[\"\"data\"\"] as $c) {\n echo \"\" - \"\" . ($c[\"\"Full_Name\"\"] ?? $c[\"\"Email\"\"] ?? \"\"Unknown\"\") . \"\"\\n\"\";\n }\n echo \"\" Total in response: \"\" . count($contacts[\"\"data\"\"]) . \"\"\\n\"\";\n} elseif (isset($contacts[\"\"info\"\"])) {\n echo \"\" No contacts found (empty CRM)\\n\"\";\n} else {\n echo \"\" Response: \"\" . print_r($contacts, true);\n}\n''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp eval ''\nrequire_once ABSPATH . \"\"wp-content/plugins/hvac-community-events/includes/zoho/class-zoho-crm-auth.php\"\";\n$auth = new HVAC_Zoho_CRM_Auth();\n\necho \"\"3. CAMPAIGNS (first 5):\\n\"\";\n$campaigns = $auth->make_api_request(\"\"/Campaigns?per_page=5\"\", \"\"GET\"\");\nif (isset($campaigns[\"\"data\"\"])) {\n foreach ($campaigns[\"\"data\"\"] as $c) {\n echo \"\" - \"\" . ($c[\"\"Campaign_Name\"\"] ?? \"\"Unnamed\"\") . \"\"\\n\"\";\n }\n} elseif (isset($campaigns[\"\"code\"\"]) && $campaigns[\"\"code\"\"] == \"\"INVALID_MODULE\"\") {\n echo \"\" Campaigns module not enabled\\n\"\";\n} else {\n echo \"\" Response: \"\" . json_encode($campaigns) . \"\"\\n\"\";\n}\n\necho \"\"\\n4. USERS (CRM users):\\n\"\";\n$users = $auth->make_api_request(\"\"/users?type=AllUsers\"\", \"\"GET\"\");\nif (isset($users[\"\"users\"\"])) {\n foreach (array_slice($users[\"\"users\"\"], 0, 5) as $u) {\n echo \"\" - \"\" . $u[\"\"full_name\"\"] . \"\" (\"\" . $u[\"\"role\"\"][\"\"name\"\"] . \"\")\\n\"\";\n }\n} else {\n echo \"\" Response: \"\" . json_encode($users) . \"\"\\n\"\";\n}\n''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp eval ''\necho \"\"=== EVENT TICKETS DATA STRUCTURE ===\"\";\necho \"\"\\n\"\";\n\n// Check what ticket-related post types exist\n$post_types = get_post_types(array(), \"\"names\"\");\n$ticket_types = array_filter($post_types, function($pt) {\n return strpos($pt, \"\"ticket\"\") !== false || strpos($pt, \"\"tec\"\") !== false || strpos($pt, \"\"tribe\"\") !== false || strpos($pt, \"\"attendee\"\") !== false || strpos($pt, \"\"order\"\") !== false;\n});\necho \"\"Ticket-related post types:\\n\"\";\nforeach ($ticket_types as $pt) {\n $count = wp_count_posts($pt);\n $total = isset($count->publish) ? $count->publish : 0;\n echo \"\" - \"\" . $pt . \"\" (\"\" . $total . \"\" published)\\n\"\";\n}\n\necho \"\"\\n\"\";\n\n// Check for Tickets Commerce orders\nif (class_exists(\"\"TEC\\Tickets\\Commerce\\Order\"\")) {\n echo \"\"Tickets Commerce Order class exists\\n\"\";\n}\n\n// Check for attendees\n$attendees = get_posts(array(\n \"\"post_type\"\" => \"\"tribe_tpp_attendees\"\",\n \"\"posts_per_page\"\" => 5,\n \"\"post_status\"\" => \"\"any\"\"\n));\necho \"\"\\nAttendees (tribe_tpp_attendees): \"\" . count($attendees) . \"\"\\n\"\";\n\n// Try tribe_rsvp_attendees\n$rsvp = get_posts(array(\n \"\"post_type\"\" => \"\"tribe_rsvp_attendees\"\", \n \"\"posts_per_page\"\" => 5,\n \"\"post_status\"\" => \"\"any\"\"\n));\necho \"\"RSVPs (tribe_rsvp_attendees): \"\" . count($rsvp) . \"\"\\n\"\";\n\n// Try tec_tc_attendee (Tickets Commerce)\n$tc_attendees = get_posts(array(\n \"\"post_type\"\" => \"\"tec_tc_attendee\"\",\n \"\"posts_per_page\"\" => 5, \n \"\"post_status\"\" => \"\"any\"\"\n));\necho \"\"TC Attendees (tec_tc_attendee): \"\" . count($tc_attendees) . \"\"\\n\"\";\n\n// Try tec_tc_order\n$tc_orders = get_posts(array(\n \"\"post_type\"\" => \"\"tec_tc_order\"\",\n \"\"posts_per_page\"\" => 5,\n \"\"post_status\"\" => \"\"any\"\"\n));\necho \"\"TC Orders (tec_tc_order): \"\" . count($tc_orders) . \"\"\\n\"\";\n''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 \"cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && wp eval ''\necho \"\"=== EVENT TICKETS DATA STRUCTURE ===\\n\\n\"\";\n\n// Check ticket-related post types\n$post_types = get_post_types(array(), \"\"names\"\");\necho \"\"Ticket-related post types:\\n\"\";\nforeach ($post_types as $pt) {\n if (strpos($pt, \"\"ticket\"\") !== false || strpos($pt, \"\"attendee\"\") !== false || strpos($pt, \"\"rsvp\"\") !== false || strpos($pt, \"\"tec_tc\"\") !== false) {\n $count = wp_count_posts($pt);\n $total = isset($count->publish) ? $count->publish : 0;\n echo \"\" - \"\" . $pt . \"\" (\"\" . $total . \"\" published)\\n\"\";\n }\n}\n''\")",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 'cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && cat > /tmp/check_orders.php << ''''PHPCODE''''\n<?php\nrequire_once(\"\"wp-load.php\"\");\n\necho \"\"=== TICKETS COMMERCE ORDERS ===\\n\\n\"\";\n\n// Get TC orders\n$orders = get_posts(array(\n \"\"post_type\"\" => \"\"tec_tc_order\"\",\n \"\"posts_per_page\"\" => 3,\n \"\"post_status\"\" => \"\"any\"\"\n));\n\necho \"\"Found \"\" . count($orders) . \"\" sample orders\\n\\n\"\";\n\nforeach ($orders as $order) {\n echo \"\"--- Order #\"\" . $order->ID . \"\" ---\\n\"\";\n echo \"\"Status: \"\" . $order->post_status . \"\"\\n\"\";\n echo \"\"Date: \"\" . $order->post_date . \"\"\\n\"\";\n \n $meta = get_post_meta($order->ID);\n \n // Key fields\n $fields = array(\n \"\"_tec_tc_order_purchaser_name\"\",\n \"\"_tec_tc_order_purchaser_email\"\", \n \"\"_tec_tc_order_total\"\",\n \"\"_tec_tc_order_gateway\"\",\n \"\"_tec_tc_order_gateway_order_id\"\",\n \"\"_tec_tc_order_items\"\"\n );\n \n foreach ($fields as $f) {\n if (isset($meta[$f])) {\n $val = $meta[$f][0];\n if (is_serialized($val)) {\n $val = \"\"SERIALIZED: \"\" . substr($val, 0, 100) . \"\"...\"\";\n }\n echo \"\"$f: $val\\n\"\";\n }\n }\n echo \"\"\\n\"\";\n}\n\n// Also check attendees linked to orders\necho \"\"=== SAMPLE ATTENDEE WITH ORDER LINK ===\\n\"\";\n$attendee = get_posts(array(\n \"\"post_type\"\" => \"\"tec_tc_attendee\"\",\n \"\"posts_per_page\"\" => 1,\n \"\"post_status\"\" => \"\"any\"\"\n));\nif ($attendee) {\n $a = $attendee[0];\n $meta = get_post_meta($a->ID);\n echo \"\"Attendee ID: \"\" . $a->ID . \"\"\\n\"\";\n echo \"\"Event ID: \"\" . ($meta[\"\"_tec_tc_attendee_event\"\"][0] ?? \"\"N/A\"\") . \"\"\\n\"\";\n echo \"\"Order ID: \"\" . ($meta[\"\"_tec_tc_order\"\"][0] ?? $meta[\"\"_tribe_tpp_order\"\"][0] ?? \"\"N/A\"\") . \"\"\\n\"\";\n echo \"\"Ticket ID: \"\" . ($meta[\"\"_tec_tc_attendee_ticket\"\"][0] ?? \"\"N/A\"\") . \"\"\\n\"\";\n echo \"\"Name: \"\" . ($meta[\"\"_tec_tc_attendee_name\"\"][0] ?? $meta[\"\"_tribe_tickets_full_name\"\"][0] ?? \"\"N/A\"\") . \"\"\\n\"\";\n echo \"\"Email: \"\" . ($meta[\"\"_tec_tc_attendee_email\"\"][0] ?? $meta[\"\"_tribe_tickets_email\"\"][0] ?? \"\"N/A\"\") . \"\"\\n\"\";\n}\nPHPCODE\nphp /tmp/check_orders.php')",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 'cd /home/974670.cloudwaysapps.com/uberrxmprk/public_html && cat > /tmp/test_sync.php << ''''PHPCODE''''\n<?php\nrequire_once(\"\"wp-load.php\"\");\nrequire_once(ABSPATH . \"\"wp-content/plugins/hvac-community-events/includes/zoho/class-zoho-sync.php\"\");\n\n$sync = new HVAC_Zoho_Sync();\n\necho \"\"=== DRY-RUN: ALL SYNC TYPES ===\\n\\n\"\";\n\n// 1. Events\necho \"\"1. EVENTS → CAMPAIGNS\\n\"\";\n$result = $sync->sync_events();\necho \"\" Total: \"\" . $result[\"\"total\"\"] . \"\", Would Sync: \"\" . $result[\"\"synced\"\"] . \"\"\\n\"\";\nif (!empty($result[\"\"test_data\"\"])) {\n $sample = $result[\"\"test_data\"\"][0];\n echo \"\" Sample: \"\" . substr($sample[\"\"event_title\"\"], 0, 40) . \"\"...\\n\"\";\n}\necho \"\"\\n\"\";\n\n// 2. Users (Trainers)\necho \"\"2. TRAINERS → CONTACTS\\n\"\";\n$result = $sync->sync_users();\necho \"\" Total: \"\" . $result[\"\"total\"\"] . \"\", Would Sync: \"\" . $result[\"\"synced\"\"] . \"\"\\n\"\";\nif (!empty($result[\"\"test_data\"\"])) {\n $sample = $result[\"\"test_data\"\"][0];\n echo \"\" Sample: \"\" . $sample[\"\"user_email\"\"] . \"\" (\"\" . $sample[\"\"user_role\"\"] . \"\")\\n\"\";\n}\necho \"\"\\n\"\";\n\n// 3. Attendees\necho \"\"3. ATTENDEES → CONTACTS + CAMPAIGN MEMBERS\\n\"\";\n$result = $sync->sync_attendees();\necho \"\" Total: \"\" . $result[\"\"total\"\"] . \"\", Would Sync: \"\" . $result[\"\"synced\"\"] . \"\"\\n\"\";\nif (!empty($result[\"\"test_data\"\"])) {\n $sample = $result[\"\"test_data\"\"][0];\n echo \"\" Sample: \"\" . ($sample[\"\"full_name\"\"] ?: \"\"Unknown\"\") . \"\" <\"\" . ($sample[\"\"email\"\"] ?: \"\"no-email\"\") . \"\">\\n\"\";\n echo \"\" Event: \"\" . ($sample[\"\"event_title\"\"] ?: \"\"N/A\"\") . \"\"\\n\"\";\n}\necho \"\"\\n\"\";\n\n// 4. RSVPs\necho \"\"4. RSVPs → LEADS + CAMPAIGN MEMBERS\\n\"\";\n$result = $sync->sync_rsvps();\necho \"\" Total: \"\" . $result[\"\"total\"\"] . \"\", Would Sync: \"\" . $result[\"\"synced\"\"] . \"\"\\n\"\";\nif (!empty($result[\"\"test_data\"\"])) {\n $sample = $result[\"\"test_data\"\"][0];\n echo \"\" Sample: \"\" . ($sample[\"\"full_name\"\"] ?: \"\"Unknown\"\") . \"\" <\"\" . ($sample[\"\"email\"\"] ?: \"\"no-email\"\") . \"\">\\n\"\";\n echo \"\" RSVP Status: \"\" . ($sample[\"\"rsvp_status\"\"] ?: \"\"N/A\"\") . \"\"\\n\"\";\n}\necho \"\"\\n\"\";\n\n// 5. Purchases/Orders\necho \"\"5. TICKET ORDERS → INVOICES\\n\"\";\n$result = $sync->sync_purchases();\necho \"\" Total: \"\" . $result[\"\"total\"\"] . \"\", Would Sync: \"\" . $result[\"\"synced\"\"] . \"\"\\n\"\";\nif (!empty($result[\"\"test_data\"\"])) {\n $sample = $result[\"\"test_data\"\"][0];\n echo \"\" Sample: Order #\"\" . $sample[\"\"order_id\"\"] . \"\" - \"\" . $sample[\"\"purchaser_email\"\"] . \"\"\\n\"\";\n echo \"\" Gateway: \"\" . $sample[\"\"gateway\"\"] . \"\", Date: \"\" . $sample[\"\"date\"\"] . \"\"\\n\"\";\n}\necho \"\"\\n\"\";\n\necho \"\"=== ALL SYNCS IN STAGING MODE (NO DATA SENT TO ZOHO) ===\\n\"\";\nPHPCODE\nphp /tmp/test_sync.php')",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 'tail -50 /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/debug.log 2>/dev/null | grep -i \"\"zoho\\|fatal\\|error\"\" | tail -20')",
|
||||
"Bash(./scripts/deploy.sh:*)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 'ls -la /home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events/assets/css/zoho-admin.css 2>/dev/null || echo \"\"File does not exist on staging\"\"')",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e ssh -o StrictHostKeyChecking=no roodev@146.190.76.204 'curl -s \"\"https://upskill-staging.measurequick.com/master-trainer/master-dashboard/\"\" 2>/dev/null | sed -n \"\"220,235p\"\"')",
|
||||
"Bash(curl:*)",
|
||||
"Bash(yes:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": [],
|
||||
|
|
|
|||
23
Status.md
23
Status.md
|
|
@ -39,6 +39,29 @@
|
|||
|
||||
---
|
||||
|
||||
## 🎯 CURRENT SESSION - PUBLIC MAP & DIRECTORY FIX (Dec 20, 2025)
|
||||
|
||||
### Status: 🚧 **IN PROGRESS (Debugging)**
|
||||
|
||||
**Problem:** "Find a Trainer" map loads briefly then disappears, replaced by "Map Temporarily Unavailable". Directory filters working but map unstable.
|
||||
|
||||
**Findings:**
|
||||
1. **Safari Blocker Bug:** Identified and fixed `HVAC_Find_Trainer_Assets` correctly blocking assets on Safari.
|
||||
2. **Safety Script Race Condition:** `mapgeo-safety.js` has a hard 6-second timeout loop that conflicts with slower resource loading.
|
||||
3. **Stale Cache Issue:** Browser continues to serve old `mapgeo-safety.js` (6s timeout) despite file update (30s timeout) and version bump (`.fix1`).
|
||||
4. **Environment Constraints:** `wp cache flush` failed (command not found), indicating potential restriction on direct WP-CLI use.
|
||||
|
||||
**Fixes Applied (Pending Verification):**
|
||||
1. ✅ **Removed Safari Blocker:** Corrected logic in `class-hvac-find-trainer-assets.php`.
|
||||
2. ✅ **Increased Timeouts:** Updated `assets/js/mapgeo-safety.js` to 30s global timeout.
|
||||
3. ✅ **Version Bump:** Added `.fix1` suffix to enqueue version in `class-hvac-mapgeo-safety.php`.
|
||||
|
||||
**Next Steps:**
|
||||
- Resolve caching issue to ensure updated `mapgeo-safety.js` is served.
|
||||
- Verify map stability with 30s timeout.
|
||||
|
||||
---
|
||||
|
||||
## 📋 PREVIOUS SESSION - SCHEDULED ZOHO SYNC (Dec 19, 2025)
|
||||
|
||||
### Status: ✅ **COMPLETE - WP-Cron Scheduled Sync Implemented**
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
const config = window.HVAC_MapGeo_Config || {
|
||||
maxRetries: 3,
|
||||
retryDelay: 2000,
|
||||
timeout: 10000,
|
||||
timeout: 30000, // Increased to 30s
|
||||
fallbackEnabled: true,
|
||||
debugMode: false
|
||||
};
|
||||
|
|
@ -252,7 +252,7 @@
|
|||
'https://cdn.amcharts.com/lib/version/4.10.29/maps.js',
|
||||
'https://cdn.amcharts.com/lib/4/geodata/usaLow.js'
|
||||
];
|
||||
this.timeout = 5000; // 5 second timeout
|
||||
this.timeout = 15000; // Increased to 15s
|
||||
this.cacheKey = 'hvac_cdn_health';
|
||||
this.cacheExpiry = 10 * 60 * 1000; // 10 minutes
|
||||
}
|
||||
|
|
@ -381,9 +381,9 @@
|
|||
if (mapLoaded) {
|
||||
log('[MapGeo Safety] Map loaded successfully');
|
||||
clearInterval(healthCheckInterval);
|
||||
} else if (healthCheckCount >= 6) {
|
||||
// Reduced to 6 seconds since we already verified CDN
|
||||
error('[MapGeo Safety] Map failed to load after 6 seconds (CDN was healthy)');
|
||||
} else if (healthCheckCount >= 30) {
|
||||
// Increased to 30 seconds to match global timeout
|
||||
error('[MapGeo Safety] Map failed to load after 30 seconds');
|
||||
clearInterval(healthCheckInterval);
|
||||
|
||||
// Activate fallback if configured
|
||||
|
|
|
|||
|
|
@ -74,6 +74,9 @@ jQuery(document).ready(function($) {
|
|||
});
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// Credentials form submission
|
||||
// =====================================================
|
||||
// =====================================================
|
||||
// Credentials form submission
|
||||
// =====================================================
|
||||
|
|
@ -87,14 +90,73 @@ jQuery(document).ready(function($) {
|
|||
nonce: $('input[name="hvac_zoho_nonce"]').val()
|
||||
};
|
||||
|
||||
$('#save-credentials').prop('disabled', true).text('Saving...');
|
||||
var $saveBtn = $('#save-credentials');
|
||||
$saveBtn.prop('disabled', true).text('Saving...');
|
||||
|
||||
$.post(hvacZoho.ajaxUrl, formData, function(response) {
|
||||
// Clear any previous messages
|
||||
$('.notice').remove();
|
||||
|
||||
$.ajax({
|
||||
url: hvacZoho.ajaxUrl,
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
window.location.href = window.location.href.split('?')[0] + '?page=hvac-zoho-sync&credentials_saved=1';
|
||||
} else {
|
||||
alert('Error saving credentials: ' + response.data.message);
|
||||
$('#save-credentials').prop('disabled', false).text('Save Credentials');
|
||||
// Create error notice
|
||||
var errorHtml = '<div class="notice notice-error is-dismissible"><p>' +
|
||||
'<strong>Error saving credentials:</strong> ' + response.data.message +
|
||||
'</p></div>';
|
||||
$('h1').after(errorHtml);
|
||||
|
||||
$saveBtn.prop('disabled', false).text('Save Credentials');
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('Zoho Save Error:', xhr);
|
||||
|
||||
var errorMessage = 'Unknown error occurred';
|
||||
var errorDetails = '';
|
||||
|
||||
if (xhr.status === 400 || xhr.status === 403) {
|
||||
errorMessage = 'Request blocked (' + xhr.status + ')';
|
||||
errorDetails = 'This is likely due to a security plugin or Web Application Firewall (WAF) blocking the request. ' +
|
||||
'The content (e.g. Client Secret) might be triggering a false positive security rule.';
|
||||
} else if (xhr.status === 500) {
|
||||
errorMessage = 'Server Error (500)';
|
||||
errorDetails = 'Check the server error logs for more information.';
|
||||
} else if (status === 'timeout') {
|
||||
errorMessage = 'Request timed out';
|
||||
} else if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
|
||||
errorMessage = xhr.responseJSON.data.message;
|
||||
} else {
|
||||
errorMessage = error || status;
|
||||
}
|
||||
|
||||
var errorHtml = '<div class="notice notice-error is-dismissible" style="border-left-color: #dc3232;">' +
|
||||
'<p><strong>❌ Save Failed:</strong> ' + errorMessage + '</p>';
|
||||
|
||||
if (errorDetails) {
|
||||
errorHtml += '<p><em>' + errorDetails + '</em></p>';
|
||||
}
|
||||
|
||||
// Add debug details
|
||||
errorHtml += '<details style="margin-top: 10px;">' +
|
||||
'<summary>Technical Details</summary>' +
|
||||
'<ul style="margin-top: 5px; font-size: 12px; background: #f0f0f0; padding: 10px;">' +
|
||||
'<li>Status: ' + xhr.status + ' ' + xhr.statusText + '</li>' +
|
||||
'<li>Response: ' + (xhr.responseText ? xhr.responseText.substring(0, 100) + '...' : '(empty)') + '</li>' +
|
||||
'</ul>' +
|
||||
'</details></div>';
|
||||
|
||||
$('h1').after(errorHtml);
|
||||
|
||||
// Re-enable button
|
||||
$saveBtn.prop('disabled', false).text('Save Credentials');
|
||||
|
||||
// Scroll to error
|
||||
$('html, body').animate({ scrollTop: 0 }, 'slow');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -277,14 +339,28 @@ jQuery(document).ready(function($) {
|
|||
});
|
||||
});
|
||||
|
||||
// Sync data
|
||||
$('.sync-button').on('click', function() {
|
||||
var $button = $(this);
|
||||
var type = $button.data('type');
|
||||
var $status = $('#' + type + '-status');
|
||||
// =====================================================
|
||||
// Sync data with batch progress
|
||||
// =====================================================
|
||||
|
||||
$button.prop('disabled', true).text('Syncing...');
|
||||
$status.html('<p>Syncing ' + type + '...</p>');
|
||||
/**
|
||||
* Sync with progress - auto-continues through all batches
|
||||
* @param {jQuery} $button - The sync button
|
||||
* @param {string} type - Sync type (events, users, attendees, rsvps, purchases)
|
||||
* @param {jQuery} $status - Status container element
|
||||
* @param {number} offset - Current offset
|
||||
* @param {object} accumulated - Accumulated results across batches
|
||||
*/
|
||||
function syncWithProgress($button, type, $status, offset, accumulated) {
|
||||
accumulated = accumulated || {
|
||||
synced: 0,
|
||||
failed: 0,
|
||||
errors: [],
|
||||
total: 0,
|
||||
staging_mode: false,
|
||||
responses: [],
|
||||
test_data: []
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: hvacZoho.ajaxUrl,
|
||||
|
|
@ -292,48 +368,153 @@ jQuery(document).ready(function($) {
|
|||
data: {
|
||||
action: 'hvac_zoho_sync_data',
|
||||
type: type,
|
||||
offset: offset,
|
||||
nonce: hvacZoho.nonce
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
var result = response.data;
|
||||
|
||||
// Update accumulated totals
|
||||
accumulated.total = result.total; // Total is consistent across batches
|
||||
accumulated.synced += result.synced;
|
||||
accumulated.failed += result.failed;
|
||||
accumulated.staging_mode = result.staging_mode;
|
||||
|
||||
// Merge arrays
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
accumulated.errors = accumulated.errors.concat(result.errors);
|
||||
}
|
||||
if (result.responses && result.responses.length > 0) {
|
||||
accumulated.responses = accumulated.responses.concat(result.responses);
|
||||
}
|
||||
if (result.test_data && result.test_data.length > 0) {
|
||||
accumulated.test_data = accumulated.test_data.concat(result.test_data);
|
||||
}
|
||||
|
||||
// Calculate progress
|
||||
var processed = accumulated.synced + accumulated.failed;
|
||||
var percent = accumulated.total > 0 ? Math.round((processed / accumulated.total) * 100) : 0;
|
||||
|
||||
// Update progress bar
|
||||
var progressHtml = '<div class="sync-progress-bar" style="margin: 10px 0;">' +
|
||||
'<div style="background: #e0e0e0; border-radius: 4px; overflow: hidden; height: 20px;">' +
|
||||
'<div style="background: linear-gradient(90deg, #0073aa, #00a0d2); height: 100%; width: ' + percent + '%; transition: width 0.3s;"></div>' +
|
||||
'</div>' +
|
||||
'<p style="margin: 5px 0; font-size: 13px;">' +
|
||||
'<strong>' + processed + ' of ' + accumulated.total + '</strong> processed (' + percent + '%)' +
|
||||
'</p></div>';
|
||||
$status.html(progressHtml);
|
||||
|
||||
// Check if there are more batches
|
||||
if (result.has_more && result.next_offset > offset) {
|
||||
// Continue with next batch
|
||||
syncWithProgress($button, type, $status, result.next_offset, accumulated);
|
||||
} else {
|
||||
// All done! Show final results
|
||||
displaySyncResults($button, type, $status, accumulated, result);
|
||||
}
|
||||
} else {
|
||||
$status.html('<div class="notice notice-error"><p>' + response.data.message + ': ' + response.data.error + '</p></div>');
|
||||
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$status.html('<div class="notice notice-error"><p>Sync failed - network or server error</p></div>');
|
||||
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display final sync results
|
||||
*/
|
||||
function displaySyncResults($button, type, $status, accumulated, lastResult) {
|
||||
var html = '<div class="notice notice-success">';
|
||||
|
||||
if (result.staging_mode) {
|
||||
html += '<h4>🔧 STAGING MODE - Simulation Results</h4>';
|
||||
html += '<p>' + result.message + '</p>';
|
||||
if (accumulated.staging_mode) {
|
||||
html += '<h4>🔧 STAGING MODE - Simulation Complete</h4>';
|
||||
html += '<p>No data was sent to Zoho CRM. This is a dry-run showing what would sync.</p>';
|
||||
} else {
|
||||
html += '<p>Sync completed successfully!</p>';
|
||||
html += '<p><strong>✅ Sync completed successfully!</strong></p>';
|
||||
}
|
||||
|
||||
if (lastResult.version) {
|
||||
html += '<p><strong>Server Code Version:</strong> ' + lastResult.version + '</p>';
|
||||
}
|
||||
|
||||
html += '<ul>' +
|
||||
'<li>Total records: ' + result.total + '</li>' +
|
||||
'<li>Synced: ' + result.synced + '</li>' +
|
||||
'<li>Failed: ' + result.failed + '</li>' +
|
||||
'<li>Total records: ' + accumulated.total + '</li>' +
|
||||
'<li>Synced: ' + accumulated.synced + '</li>' +
|
||||
'<li>Failed: ' + accumulated.failed + '</li>' +
|
||||
'</ul>';
|
||||
|
||||
if (result.test_data && result.test_data.length > 0) {
|
||||
// Show test data for staging
|
||||
if (accumulated.test_data && accumulated.test_data.length > 0) {
|
||||
html += '<details>' +
|
||||
'<summary>View test data (first 5 records)</summary>' +
|
||||
'<pre style="background: #f0f0f0; padding: 10px; overflow: auto;">' +
|
||||
JSON.stringify(result.test_data.slice(0, 5), null, 2) +
|
||||
'<pre style="background: #f0f0f0; padding: 10px; overflow: auto; max-height: 300px;">' +
|
||||
JSON.stringify(accumulated.test_data.slice(0, 5), null, 2) +
|
||||
'</pre>' +
|
||||
'</details>';
|
||||
}
|
||||
|
||||
// Debug info
|
||||
if (lastResult.debug_info) {
|
||||
html += '<details style="margin-top: 10px;">' +
|
||||
'<summary>🔍 Debug: Mode Detection Info</summary>' +
|
||||
'<div style="background: #f0f0f0; padding: 10px; font-size: 12px; margin-top: 5px;">';
|
||||
|
||||
if (typeof lastResult.debug_info.is_staging !== 'undefined') {
|
||||
html += '<p><strong>Is Staging:</strong> ' + (lastResult.debug_info.is_staging ? '✅ YES' : '❌ NO') + '</p>';
|
||||
}
|
||||
if (lastResult.debug_info.site_url) {
|
||||
html += '<p><strong>Site URL:</strong> ' + lastResult.debug_info.site_url + '</p>';
|
||||
}
|
||||
|
||||
html += '<pre style="background: #e0e0e0; padding: 5px; margin-top: 5px; max-height: 150px; overflow: auto;">' +
|
||||
JSON.stringify(lastResult.debug_info, null, 2) +
|
||||
'</pre>' +
|
||||
'</div>' +
|
||||
'</details>';
|
||||
}
|
||||
|
||||
// Show errors if any
|
||||
if (accumulated.errors && accumulated.errors.length > 0) {
|
||||
html += '<details style="margin-top: 10px; border: 2px solid #dc3232;">' +
|
||||
'<summary style="font-weight: bold; color: #dc3232;">❌ Errors (' + accumulated.errors.length + ')</summary>' +
|
||||
'<pre style="background: #fff0f0; padding: 10px; overflow: auto; max-height: 300px;">' +
|
||||
JSON.stringify(accumulated.errors.slice(0, 20), null, 2) +
|
||||
'</pre>' +
|
||||
'</details>';
|
||||
}
|
||||
|
||||
// Show API responses preview
|
||||
if (accumulated.responses && accumulated.responses.length > 0) {
|
||||
html += '<details style="margin-top: 10px;">' +
|
||||
'<summary>📡 Raw API Responses (first 10)</summary>' +
|
||||
'<pre style="background: #f0f0f0; padding: 10px; overflow: auto; max-height: 200px;">' +
|
||||
JSON.stringify(accumulated.responses.slice(0, 10), null, 2) +
|
||||
'</pre>' +
|
||||
'</details>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
$status.html(html);
|
||||
} else {
|
||||
$status.html('<div class="notice notice-error"><p>' + response.data.message + ': ' + response.data.error + '</p></div>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$status.html('<div class="notice notice-error"><p>Sync failed</p></div>');
|
||||
},
|
||||
complete: function() {
|
||||
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
|
||||
}
|
||||
});
|
||||
|
||||
// Sync button click handler
|
||||
$('.sync-button').on('click', function () {
|
||||
var $button = $(this);
|
||||
var type = $button.data('type');
|
||||
var $status = $('#' + type + '-status');
|
||||
|
||||
$button.prop('disabled', true).text('Syncing...');
|
||||
$status.html('<p>Starting sync for ' + type + '...</p>');
|
||||
|
||||
// Start sync with batch progress
|
||||
syncWithProgress($button, type, $status, 0, null);
|
||||
});
|
||||
|
||||
// Save settings
|
||||
|
|
@ -356,12 +537,8 @@ jQuery(document).ready(function($) {
|
|||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
// Use toast notification instead of alert
|
||||
if (window.HVACToast) {
|
||||
HVACToast.success('Settings saved successfully!');
|
||||
} else {
|
||||
alert('Settings saved successfully!');
|
||||
}
|
||||
// Reload page to show updated status
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Use toast notification instead of alert
|
||||
if (window.HVACToast) {
|
||||
|
|
@ -369,6 +546,7 @@ jQuery(document).ready(function($) {
|
|||
} else {
|
||||
alert('Error saving settings: ' + response.data.message);
|
||||
}
|
||||
$button.prop('disabled', false).text('Save Settings');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
|
|
@ -378,10 +556,151 @@ jQuery(document).ready(function($) {
|
|||
} else {
|
||||
alert('Error saving settings');
|
||||
}
|
||||
},
|
||||
complete: function() {
|
||||
$button.prop('disabled', false).text('Save Settings');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// Run Scheduled Sync Now Handler
|
||||
// =====================================================
|
||||
$('#run-scheduled-sync-now').on('click', function () {
|
||||
var $button = $(this);
|
||||
var $status = $('#scheduled-sync-status');
|
||||
|
||||
$button.prop('disabled', true).text('Running...');
|
||||
$status.html('<p>Starting scheduled sync...</p>');
|
||||
|
||||
$.ajax({
|
||||
url: hvacZoho.ajaxUrl,
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: 'hvac_zoho_run_scheduled_sync',
|
||||
nonce: hvacZoho.nonce
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
var result = response.data.result;
|
||||
var html = '<div class="notice notice-success">';
|
||||
|
||||
if (result.events && result.events.staging_mode) {
|
||||
html += '<h4>🔧 STAGING MODE - Simulation Complete</h4>';
|
||||
html += '<p>No data was sent to Zoho CRM.</p>';
|
||||
} else {
|
||||
html += '<p><strong>✅ Scheduled sync completed!</strong></p>';
|
||||
}
|
||||
|
||||
html += '<ul>';
|
||||
html += '<li>Total synced: ' + (result.total_synced || 0) + '</li>';
|
||||
html += '<li>Total failed: ' + (result.total_failed || 0) + '</li>';
|
||||
html += '<li>Duration: ' + (result.duration_seconds || 0) + ' seconds</li>';
|
||||
html += '</ul>';
|
||||
|
||||
// Show details per type
|
||||
html += '<details><summary>Details by type</summary><ul>';
|
||||
['events', 'users', 'attendees', 'rsvps', 'purchases'].forEach(function (type) {
|
||||
if (result[type]) {
|
||||
html += '<li><strong>' + type + ':</strong> ' +
|
||||
(result[type].synced || 0) + ' synced, ' +
|
||||
(result[type].failed || 0) + ' failed</li>';
|
||||
}
|
||||
});
|
||||
html += '</ul></details>';
|
||||
|
||||
html += '</div>';
|
||||
$status.html(html);
|
||||
} else {
|
||||
$status.html('<div class="notice notice-error"><p>Sync failed: ' + response.data.message + '</p></div>');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$status.html('<div class="notice notice-error"><p>Error running scheduled sync</p></div>');
|
||||
},
|
||||
complete: function () {
|
||||
$button.prop('disabled', false).text('🔄 Run Sync Now');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// Diagnostic Test Handler
|
||||
// =====================================================
|
||||
$('#diagnostic-test').on('click', function () {
|
||||
var $button = $(this);
|
||||
$button.prop('disabled', true).text('Testing...');
|
||||
|
||||
// Remove existing notices
|
||||
$('.notice').remove();
|
||||
|
||||
// Test 1: Simple GET
|
||||
var runSimpleTest = function () {
|
||||
return $.ajax({
|
||||
url: hvacZoho.ajaxUrl,
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: 'hvac_zoho_simple_test'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Test 2: Payload Test (simulates credentials)
|
||||
var runPayloadTest = function () {
|
||||
var fakeId = '1000.' + new Array(20).join('a');
|
||||
var fakeSecret = new Array(30).join('b');
|
||||
|
||||
return $.ajax({
|
||||
url: hvacZoho.ajaxUrl,
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: 'hvac_zoho_simple_test',
|
||||
test_payload: 'SIMULATED_CREDENTIALS',
|
||||
zoho_client_id: fakeId,
|
||||
zoho_client_secret: fakeSecret
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Execute tests sequence
|
||||
runSimpleTest()
|
||||
.then(function (response) {
|
||||
if (response.success) {
|
||||
console.log('Simple test passed');
|
||||
return runPayloadTest();
|
||||
} else {
|
||||
return $.Deferred().reject({ status: 200, statusText: 'OK', responseJSON: response });
|
||||
}
|
||||
})
|
||||
.then(function (response) {
|
||||
if (response.success) {
|
||||
// Success!
|
||||
var successHtml = '<div class="notice notice-success is-dismissible">' +
|
||||
'<p><strong>✅ Diagnostic Test Passed</strong></p>' +
|
||||
'<p>AJAX requests are working correctly. No WAF blocking detected for credential-like data.</p>' +
|
||||
'</div>';
|
||||
$('h1').after(successHtml);
|
||||
} else {
|
||||
return $.Deferred().reject({ status: 200, statusText: 'OK', responseJSON: response });
|
||||
}
|
||||
})
|
||||
.fail(function (xhr) {
|
||||
var errorHtml = '<div class="notice notice-error is-dismissible">' +
|
||||
'<p><strong>❌ Diagnostic Test Failed</strong></p>';
|
||||
|
||||
if (xhr.status === 400 || xhr.status === 403) {
|
||||
errorHtml += '<p><strong>WAF Blocking Detected!</strong></p>';
|
||||
errorHtml += '<p>The server returned ' + xhr.status + ' when sending data.</p>';
|
||||
} else {
|
||||
errorHtml += '<p>Status: ' + (xhr.status || 'Unknown') + '</p>';
|
||||
if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
|
||||
errorHtml += '<p>Message: ' + xhr.responseJSON.data.message + '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
errorHtml += '</div>';
|
||||
$('h1').after(errorHtml);
|
||||
})
|
||||
.always(function () {
|
||||
$button.prop('disabled', false).text('🏥 Run Diagnostic Test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -54,19 +54,11 @@ class HVAC_Find_Trainer_Assets {
|
|||
* Initialize WordPress hooks
|
||||
*/
|
||||
private function init_hooks() {
|
||||
// CRITICAL: Don't add asset loading hooks for Safari browsers
|
||||
// Let HVAC_Scripts_Styles handle Safari minimal loading
|
||||
if ($this->browser_detection->is_safari_browser()) {
|
||||
error_log('[HVAC Find Trainer Assets] Safari detected - skipping asset hooks to prevent resource cascade');
|
||||
// Only add footer scripts for MapGeo integration
|
||||
add_action('wp_footer', [$this, 'add_find_trainer_inline_scripts']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use proper WordPress hook system for non-Safari browsers
|
||||
// Use proper WordPress hook system
|
||||
add_action('wp_enqueue_scripts', [$this, 'enqueue_find_trainer_assets']);
|
||||
add_action('wp_footer', [$this, 'add_find_trainer_inline_scripts']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page is find-a-trainer
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class HVAC_MapGeo_Safety {
|
|||
'hvac-mapgeo-safety',
|
||||
HVAC_PLUGIN_URL . 'assets/js/mapgeo-safety.js',
|
||||
array(),
|
||||
HVAC_PLUGIN_VERSION,
|
||||
HVAC_PLUGIN_VERSION . '.fix1',
|
||||
false // Load in head to catch errors early
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,211 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Zoho CRM Admin Interface
|
||||
*
|
||||
* Provides WordPress admin interface for Zoho credential management
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class HVAC_Zoho_Admin {
|
||||
|
||||
public function __construct() {
|
||||
add_action('admin_menu', array($this, 'add_admin_menu'));
|
||||
add_action('admin_init', array($this, 'handle_auth_callback'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu item to WordPress admin
|
||||
*/
|
||||
public function add_admin_menu() {
|
||||
add_submenu_page(
|
||||
'edit.php?post_type=tribe_events',
|
||||
'Zoho CRM Integration',
|
||||
'Zoho CRM',
|
||||
'manage_options',
|
||||
'hvac-zoho-crm',
|
||||
array($this, 'admin_page')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle OAuth callback
|
||||
*/
|
||||
public function handle_auth_callback() {
|
||||
if (isset($_GET['page']) && $_GET['page'] === 'hvac-zoho-crm' && isset($_GET['code'])) {
|
||||
$auth = new HVAC_Zoho_CRM_Auth();
|
||||
|
||||
if ($auth->exchange_code_for_tokens($_GET['code'])) {
|
||||
add_settings_error(
|
||||
'hvac_zoho_messages',
|
||||
'hvac_zoho_auth_success',
|
||||
'Successfully connected to Zoho CRM!',
|
||||
'success'
|
||||
);
|
||||
} else {
|
||||
add_settings_error(
|
||||
'hvac_zoho_messages',
|
||||
'hvac_zoho_auth_error',
|
||||
'Failed to connect to Zoho CRM. Please check your credentials.',
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
// Redirect to remove code from URL
|
||||
wp_redirect(admin_url('edit.php?post_type=tribe_events&page=hvac-zoho-crm'));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display admin page
|
||||
*/
|
||||
public function admin_page() {
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>Zoho CRM Integration</h1>
|
||||
|
||||
<?php settings_errors('hvac_zoho_messages'); ?>
|
||||
|
||||
<?php
|
||||
// Check if config file exists
|
||||
$config_file = plugin_dir_path(dirname(__FILE__)) . 'zoho/zoho-config.php';
|
||||
$config_exists = file_exists($config_file);
|
||||
|
||||
if (!$config_exists):
|
||||
?>
|
||||
<div class="notice notice-warning">
|
||||
<p>Zoho CRM configuration file not found. Please follow the setup instructions below.</p>
|
||||
</div>
|
||||
|
||||
<h2>Setup Instructions</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<strong>Register your application in Zoho:</strong>
|
||||
<a href="https://api-console.zoho.com/" target="_blank">Go to Zoho API Console</a>
|
||||
</li>
|
||||
<li>Create a new Server-based Application</li>
|
||||
<li>Set redirect URI to: <code><?php echo admin_url('edit.php?post_type=tribe_events&page=hvac-zoho-crm'); ?></code></li>
|
||||
<li>Copy your Client ID and Client Secret</li>
|
||||
<li>Run the setup helper script from command line:
|
||||
<pre>cd <?php echo plugin_dir_path(dirname(__FILE__)); ?>zoho
|
||||
php setup-helper.php</pre>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<?php
|
||||
// Load configuration
|
||||
require_once $config_file;
|
||||
$auth = new HVAC_Zoho_CRM_Auth();
|
||||
|
||||
// Test connection
|
||||
$org_info = $auth->make_api_request('/crm/v2/org');
|
||||
$connected = !is_wp_error($org_info) && isset($org_info['org']);
|
||||
?>
|
||||
|
||||
<?php if ($connected): ?>
|
||||
<div class="notice notice-success">
|
||||
<p>✓ Connected to Zoho CRM</p>
|
||||
</div>
|
||||
|
||||
<h2>Organization Information</h2>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th>Organization Name</th>
|
||||
<td><?php echo esc_html($org_info['org'][0]['company_name']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Organization ID</th>
|
||||
<td><?php echo esc_html($org_info['org'][0]['id']); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Time Zone</th>
|
||||
<td><?php echo esc_html($org_info['org'][0]['time_zone']); ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Integration Status</h2>
|
||||
<?php $this->display_integration_status(); ?>
|
||||
|
||||
<h2>Actions</h2>
|
||||
<p>
|
||||
<a href="<?php echo wp_nonce_url(admin_url('edit.php?post_type=tribe_events&page=hvac-zoho-crm&action=test_sync'), 'test_sync'); ?>"
|
||||
class="button button-primary">Test Sync</a>
|
||||
<a href="<?php echo wp_nonce_url(admin_url('edit.php?post_type=tribe_events&page=hvac-zoho-crm&action=create_fields'), 'create_fields'); ?>"
|
||||
class="button">Create Custom Fields</a>
|
||||
</p>
|
||||
|
||||
<?php else: ?>
|
||||
<div class="notice notice-error">
|
||||
<p>✗ Not connected to Zoho CRM</p>
|
||||
</div>
|
||||
|
||||
<h2>Reconnect to Zoho</h2>
|
||||
<p>Click the button below to authorize this application with Zoho CRM:</p>
|
||||
<p>
|
||||
<a href="<?php echo esc_url($auth->get_authorization_url()); ?>"
|
||||
class="button button-primary">Connect to Zoho CRM</a>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Display integration status
|
||||
*/
|
||||
private function display_integration_status() {
|
||||
?>
|
||||
<table class="widefat striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Module</th>
|
||||
<th>Fields Configured</th>
|
||||
<th>Last Sync</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Campaigns (Events)</td>
|
||||
<td><?php echo $this->check_custom_fields('Campaigns'); ?></td>
|
||||
<td><?php echo get_option('hvac_zoho_last_campaign_sync', 'Never'); ?></td>
|
||||
<td><span class="dashicons dashicons-yes-alt" style="color: green;"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Contacts (Users)</td>
|
||||
<td><?php echo $this->check_custom_fields('Contacts'); ?></td>
|
||||
<td><?php echo get_option('hvac_zoho_last_contact_sync', 'Never'); ?></td>
|
||||
<td><span class="dashicons dashicons-yes-alt" style="color: green;"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Invoices (Orders)</td>
|
||||
<td><?php echo $this->check_custom_fields('Invoices'); ?></td>
|
||||
<td><?php echo get_option('hvac_zoho_last_invoice_sync', 'Never'); ?></td>
|
||||
<td><span class="dashicons dashicons-yes-alt" style="color: green;"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if custom fields exist
|
||||
*/
|
||||
private function check_custom_fields($module) {
|
||||
// This would actually check via API if the custom fields exist
|
||||
// For now, return a placeholder
|
||||
return '<span style="color: orange;">Pending</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize admin interface
|
||||
if (is_admin()) {
|
||||
new HVAC_Zoho_Admin();
|
||||
}
|
||||
|
|
@ -203,13 +203,83 @@ class HVAC_Zoho_CRM_Auth {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make authenticated API request
|
||||
*/
|
||||
/**
|
||||
* Check if we are in staging mode
|
||||
*
|
||||
* @return bool True if in staging mode
|
||||
*/
|
||||
public static function is_staging_mode() {
|
||||
// 1. Allow forcing production mode via constant
|
||||
if (defined('HVAC_ZOHO_PRODUCTION_MODE') && HVAC_ZOHO_PRODUCTION_MODE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Allow forcing staging mode via constant
|
||||
if (defined('HVAC_ZOHO_STAGING_MODE') && HVAC_ZOHO_STAGING_MODE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$site_url = get_site_url();
|
||||
|
||||
// 3. Check for specific staging domains or keywords
|
||||
if (strpos($site_url, 'staging') !== false ||
|
||||
strpos($site_url, 'dev') !== false ||
|
||||
strpos($site_url, 'test') !== false ||
|
||||
strpos($site_url, 'cloudwaysapps.com') !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. Default check: Production only on upskillhvac.com
|
||||
return strpos($site_url, 'upskillhvac.com') === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details about how the mode was determined (for debugging)
|
||||
*
|
||||
* @return array Debug information
|
||||
*/
|
||||
public static function get_debug_mode_info() {
|
||||
$info = array(
|
||||
'site_url' => get_site_url(),
|
||||
'is_staging' => self::is_staging_mode(),
|
||||
'forced_production' => defined('HVAC_ZOHO_PRODUCTION_MODE') && HVAC_ZOHO_PRODUCTION_MODE,
|
||||
'forced_staging' => defined('HVAC_ZOHO_STAGING_MODE') && HVAC_ZOHO_STAGING_MODE,
|
||||
'detection_logic' => array()
|
||||
);
|
||||
|
||||
// Replicate logic to show which rule matched
|
||||
if ($info['forced_production']) {
|
||||
$info['detection_logic'][] = 'Forced PRODUCTION via HVAC_ZOHO_PRODUCTION_MODE constant';
|
||||
} elseif ($info['forced_staging']) {
|
||||
$info['detection_logic'][] = 'Forced STAGING via HVAC_ZOHO_STAGING_MODE constant';
|
||||
} else {
|
||||
$site_url = $info['site_url'];
|
||||
if (strpos($site_url, 'staging') !== false) $info['detection_logic'][] = 'Matched "staging" in URL';
|
||||
if (strpos($site_url, 'dev') !== false) $info['detection_logic'][] = 'Matched "dev" in URL';
|
||||
if (strpos($site_url, 'test') !== false) $info['detection_logic'][] = 'Matched "test" in URL';
|
||||
if (strpos($site_url, 'cloudwaysapps.com') !== false) $info['detection_logic'][] = 'Matched "cloudwaysapps.com" in URL';
|
||||
|
||||
if (empty($info['detection_logic'])) {
|
||||
if (strpos($site_url, 'upskillhvac.com') === false) {
|
||||
$info['detection_logic'][] = 'Default STAGING: URL does not contain "upskillhvac.com"';
|
||||
} else {
|
||||
$info['detection_logic'][] = 'Default PRODUCTION: URL contains "upskillhvac.com"';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make authenticated API request
|
||||
*/
|
||||
public function make_api_request($endpoint, $method = 'GET', $data = null) {
|
||||
// Check if we're in staging mode
|
||||
$site_url = get_site_url();
|
||||
$is_staging = strpos($site_url, 'upskillhvac.com') === false;
|
||||
$is_staging = self::is_staging_mode();
|
||||
|
||||
// In staging mode, only allow read operations, no writes
|
||||
if ($is_staging && in_array($method, array('POST', 'PUT', 'DELETE', 'PATCH'))) {
|
||||
|
|
@ -258,7 +328,8 @@ class HVAC_Zoho_CRM_Auth {
|
|||
return new WP_Error('no_token', $error_message);
|
||||
}
|
||||
|
||||
$url = 'https://www.zohoapis.com/crm/v2' . $endpoint;
|
||||
// Update to v6 API (v2 is deprecated)
|
||||
$url = 'https://www.zohoapis.com/crm/v6' . $endpoint;
|
||||
|
||||
// Log the request details
|
||||
$this->log_debug('Making ' . $method . ' request to: ' . $url);
|
||||
|
|
@ -327,7 +398,8 @@ class HVAC_Zoho_CRM_Auth {
|
|||
return array(
|
||||
'error' => $error_message,
|
||||
'code' => 'JSON_PARSE_ERROR',
|
||||
'details' => 'Raw response: ' . substr($body, 0, 255) . (strlen($body) > 255 ? '...' : '')
|
||||
'details' => 'Raw response: ' . substr($body, 0, 500),
|
||||
'request_payload' => isset($args['body']) ? $args['body'] : 'No body'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -344,6 +416,7 @@ class HVAC_Zoho_CRM_Auth {
|
|||
// Add HTTP error information to the response
|
||||
$data['http_status'] = $status_code;
|
||||
$data['error'] = $error_message;
|
||||
$data['request_payload'] = isset($args['body']) ? $args['body'] : 'No body';
|
||||
|
||||
// Extract more detailed error information if available
|
||||
if (isset($data['code'])) {
|
||||
|
|
@ -406,7 +479,7 @@ class HVAC_Zoho_CRM_Auth {
|
|||
/**
|
||||
* Log debug messages
|
||||
*/
|
||||
private function log_debug($message) {
|
||||
public function log_debug($message) {
|
||||
// Sanitize message to remove sensitive data
|
||||
$sanitized = $this->sanitize_log_message($message);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ class HVAC_Zoho_Sync {
|
|||
*/
|
||||
private $is_staging;
|
||||
|
||||
/**
|
||||
* Last contact creation error for debugging
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $last_contact_error = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
|
@ -36,8 +43,7 @@ class HVAC_Zoho_Sync {
|
|||
$this->auth = new HVAC_Zoho_CRM_Auth();
|
||||
|
||||
// Determine if we're in staging mode
|
||||
$site_url = get_site_url();
|
||||
$this->is_staging = strpos($site_url, 'upskillhvac.com') === false;
|
||||
$this->is_staging = HVAC_Zoho_CRM_Auth::is_staging_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,33 +52,88 @@ class HVAC_Zoho_Sync {
|
|||
* @return bool
|
||||
*/
|
||||
private function is_sync_allowed() {
|
||||
// Only allow sync on production (upskillhvac.com)
|
||||
$site_url = get_site_url();
|
||||
return strpos($site_url, 'upskillhvac.com') !== false;
|
||||
// Use consistent logic from Auth class
|
||||
return !HVAC_Zoho_CRM_Auth::is_staging_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync events to Zoho Campaigns
|
||||
*
|
||||
* @param int $offset Starting offset for pagination
|
||||
* @param int $limit Number of records per batch
|
||||
* @param int|null $since_timestamp Optional timestamp to filter modified records
|
||||
* @return array Sync results
|
||||
*/
|
||||
public function sync_events() {
|
||||
public function sync_events($offset = 0, $limit = 50, $since_timestamp = null) {
|
||||
$results = array(
|
||||
'total' => 0,
|
||||
'synced' => 0,
|
||||
'failed' => 0,
|
||||
'errors' => array(),
|
||||
'staging_mode' => $this->is_staging
|
||||
'staging_mode' => $this->is_staging,
|
||||
'debug_info' => HVAC_Zoho_CRM_Auth::get_debug_mode_info(),
|
||||
'has_more' => false,
|
||||
'next_offset' => 0,
|
||||
'batch_offset' => $offset,
|
||||
'incremental_sync' => !is_null($since_timestamp)
|
||||
);
|
||||
|
||||
// Get all published events (past and future - 'custom' bypasses date filtering)
|
||||
$events = tribe_get_events(array(
|
||||
// Build base query args
|
||||
$base_event_args = array(
|
||||
'posts_per_page' => -1,
|
||||
'eventDisplay' => 'custom',
|
||||
'start_date' => '2020-01-01', // Include all historical events
|
||||
));
|
||||
'start_date' => '2020-01-01',
|
||||
);
|
||||
|
||||
$results['total'] = count($events);
|
||||
$base_series_args = array(
|
||||
'post_type' => 'tribe_event_series',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'publish',
|
||||
);
|
||||
|
||||
// Add date filter for incremental sync
|
||||
if ($since_timestamp) {
|
||||
$since_date = date('Y-m-d H:i:s', $since_timestamp);
|
||||
$date_query = array(
|
||||
array(
|
||||
'column' => 'post_modified',
|
||||
'after' => $since_date,
|
||||
'inclusive' => true,
|
||||
)
|
||||
);
|
||||
$base_event_args['date_query'] = $date_query;
|
||||
$base_series_args['date_query'] = $date_query;
|
||||
}
|
||||
|
||||
// First, get total count
|
||||
$all_events = tribe_get_events($base_event_args);
|
||||
$event_series = get_posts($base_series_args);
|
||||
$total_count = count($all_events) + count($event_series);
|
||||
$results['total'] = $total_count;
|
||||
|
||||
// Get paginated events
|
||||
$paginated_args = array_merge($base_event_args, array(
|
||||
'posts_per_page' => $limit,
|
||||
'offset' => $offset,
|
||||
));
|
||||
$events = tribe_get_events($paginated_args);
|
||||
|
||||
// Also get event series (paginated based on remaining limit)
|
||||
$remaining = max(0, $limit - count($events));
|
||||
$series_offset = max(0, $offset - count($all_events));
|
||||
if ($remaining > 0 && $series_offset >= 0) {
|
||||
$event_series_batch = get_posts(array(
|
||||
'post_type' => 'tribe_event_series',
|
||||
'posts_per_page' => $remaining,
|
||||
'offset' => $series_offset,
|
||||
'post_status' => 'publish',
|
||||
));
|
||||
$events = array_merge($events, $event_series_batch);
|
||||
}
|
||||
|
||||
// Calculate pagination
|
||||
$results['has_more'] = ($offset + count($events)) < $total_count;
|
||||
$results['next_offset'] = $offset + count($events);
|
||||
|
||||
// If staging mode, simulate the sync
|
||||
if ($this->is_staging) {
|
||||
|
|
@ -101,29 +162,49 @@ class HVAC_Zoho_Sync {
|
|||
foreach ($events as $event) {
|
||||
try {
|
||||
$campaign_data = $this->prepare_campaign_data($event);
|
||||
$campaign_id = null;
|
||||
|
||||
// Check if campaign already exists in Zoho
|
||||
$search_response = $this->auth->make_api_request('GET', '/crm/v2/Campaigns/search', array(
|
||||
'criteria' => "(Campaign_Name:equals:{$campaign_data['Campaign_Name']})"
|
||||
));
|
||||
// FIRST: Check if we already have a stored Zoho Campaign ID
|
||||
$stored_campaign_id = get_post_meta($event->ID, '_zoho_campaign_id', true);
|
||||
|
||||
if (!empty($search_response['data'])) {
|
||||
// Update existing campaign
|
||||
$campaign_id = $search_response['data'][0]['id'];
|
||||
$update_response = $this->auth->make_api_request('PUT', "/crm/v2/Campaigns/{$campaign_id}", array(
|
||||
if (!empty($stored_campaign_id)) {
|
||||
// We have a stored ID - try to update
|
||||
$update_response = $this->auth->make_api_request("/Campaigns/{$stored_campaign_id}", 'PUT', array(
|
||||
'data' => array($campaign_data)
|
||||
));
|
||||
|
||||
// Check if update failed due to invalid ID (e.g. campaign deleted in Zoho)
|
||||
if (isset($update_response['code']) && $update_response['code'] === 'INVALID_DATA') {
|
||||
// Fallback: Create new campaign
|
||||
$create_response = $this->auth->make_api_request('/Campaigns', 'POST', array(
|
||||
'data' => array($campaign_data)
|
||||
));
|
||||
$results['responses'][] = array('type' => 'create_fallback', 'id' => $event->ID, 'response' => $create_response);
|
||||
|
||||
if (!empty($create_response['data'][0]['details']['id'])) {
|
||||
$campaign_id = $create_response['data'][0]['details']['id'];
|
||||
}
|
||||
} else {
|
||||
// Create new campaign
|
||||
$create_response = $this->auth->make_api_request('POST', '/crm/v2/Campaigns', array(
|
||||
$campaign_id = $stored_campaign_id;
|
||||
$results['responses'][] = array('type' => 'update', 'id' => $event->ID, 'response' => $update_response);
|
||||
}
|
||||
} else {
|
||||
// No stored ID - create new campaign
|
||||
$create_response = $this->auth->make_api_request('/Campaigns', 'POST', array(
|
||||
'data' => array($campaign_data)
|
||||
));
|
||||
$results['responses'][] = array('type' => 'create', 'id' => $event->ID, 'response' => $create_response);
|
||||
|
||||
// Extract campaign ID from create response
|
||||
if (!empty($create_response['data'][0]['details']['id'])) {
|
||||
$campaign_id = $create_response['data'][0]['details']['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$results['synced']++;
|
||||
|
||||
// Update event meta with Zoho ID
|
||||
if (isset($campaign_id)) {
|
||||
// Update event meta with Zoho Campaign ID
|
||||
if (!empty($campaign_id)) {
|
||||
update_post_meta($event->ID, '_zoho_campaign_id', $campaign_id);
|
||||
}
|
||||
|
||||
|
|
@ -139,19 +220,26 @@ class HVAC_Zoho_Sync {
|
|||
/**
|
||||
* Sync users to Zoho Contacts
|
||||
*
|
||||
* @param int $offset Starting offset for pagination
|
||||
* @param int $limit Number of records per batch
|
||||
* @param int|null $since_timestamp Optional timestamp to filter modified records
|
||||
* @return array Sync results
|
||||
*/
|
||||
public function sync_users() {
|
||||
public function sync_users($offset = 0, $limit = 50, $since_timestamp = null) {
|
||||
$results = array(
|
||||
'total' => 0,
|
||||
'synced' => 0,
|
||||
'failed' => 0,
|
||||
'errors' => array(),
|
||||
'staging_mode' => $this->is_staging
|
||||
'staging_mode' => $this->is_staging,
|
||||
'has_more' => false,
|
||||
'next_offset' => 0,
|
||||
'batch_offset' => $offset,
|
||||
'incremental_sync' => !is_null($since_timestamp)
|
||||
);
|
||||
|
||||
// Get trainers (hvac_trainer and hvac_master_trainer roles)
|
||||
$users = get_users(array(
|
||||
// Base query args
|
||||
$base_args = array(
|
||||
'role__in' => array('hvac_trainer', 'hvac_master_trainer'),
|
||||
'meta_query' => array(
|
||||
'relation' => 'OR',
|
||||
|
|
@ -165,9 +253,26 @@ class HVAC_Zoho_Sync {
|
|||
'compare' => 'NOT EXISTS'
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
$results['total'] = count($users);
|
||||
// Note: WP_User_Query doesn't support date_query for user_registered
|
||||
// For users, we'll sync all and let Zoho handle updates
|
||||
// Future enhancement: track _zoho_last_synced per user
|
||||
|
||||
// Get total count first
|
||||
$total_query = new WP_User_Query(array_merge($base_args, array('count_total' => true, 'number' => -1)));
|
||||
$total_count = $total_query->get_total();
|
||||
$results['total'] = $total_count;
|
||||
|
||||
// Get paginated users
|
||||
$users = get_users(array_merge($base_args, array(
|
||||
'number' => $limit,
|
||||
'offset' => $offset
|
||||
)));
|
||||
|
||||
// Calculate pagination
|
||||
$results['has_more'] = ($offset + count($users)) < $total_count;
|
||||
$results['next_offset'] = $offset + count($users);
|
||||
|
||||
// If staging mode, simulate the sync
|
||||
if ($this->is_staging) {
|
||||
|
|
@ -199,19 +304,19 @@ class HVAC_Zoho_Sync {
|
|||
$contact_data = $this->prepare_contact_data($user);
|
||||
|
||||
// Check if contact already exists in Zoho
|
||||
$search_response = $this->auth->make_api_request('GET', '/crm/v2/Contacts/search', array(
|
||||
$search_response = $this->auth->make_api_request('/Contacts/search', 'GET', array(
|
||||
'criteria' => "(Email:equals:{$contact_data['Email']})"
|
||||
));
|
||||
|
||||
if (!empty($search_response['data'])) {
|
||||
// Update existing contact
|
||||
$contact_id = $search_response['data'][0]['id'];
|
||||
$update_response = $this->auth->make_api_request('PUT', "/crm/v2/Contacts/{$contact_id}", array(
|
||||
$update_response = $this->auth->make_api_request("/Contacts/{$contact_id}", 'PUT', array(
|
||||
'data' => array($contact_data)
|
||||
));
|
||||
} else {
|
||||
// Create new contact
|
||||
$create_response = $this->auth->make_api_request('POST', '/crm/v2/Contacts', array(
|
||||
$create_response = $this->auth->make_api_request('/Contacts', 'POST', array(
|
||||
'data' => array($contact_data)
|
||||
));
|
||||
|
||||
|
|
@ -239,31 +344,65 @@ class HVAC_Zoho_Sync {
|
|||
/**
|
||||
* Sync Event Tickets orders to Zoho Invoices
|
||||
*
|
||||
* @param int $offset Starting offset for pagination
|
||||
* @param int $limit Number of records per batch
|
||||
* @param int|null $since_timestamp Optional timestamp to filter modified records
|
||||
* @return array Sync results
|
||||
*/
|
||||
public function sync_purchases() {
|
||||
public function sync_purchases($offset = 0, $limit = 50, $since_timestamp = null) {
|
||||
$results = array(
|
||||
'total' => 0,
|
||||
'synced' => 0,
|
||||
'failed' => 0,
|
||||
'errors' => array(),
|
||||
'staging_mode' => $this->is_staging
|
||||
'staging_mode' => $this->is_staging,
|
||||
'has_more' => false,
|
||||
'next_offset' => 0,
|
||||
'batch_offset' => $offset,
|
||||
'incremental_sync' => !is_null($since_timestamp)
|
||||
);
|
||||
|
||||
// Get Tickets Commerce orders (tec_tc_order post type)
|
||||
$orders = get_posts(array(
|
||||
// Build query args
|
||||
$query_args = array(
|
||||
'post_type' => 'tec_tc_order',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'tec-tc-completed',
|
||||
));
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids'
|
||||
);
|
||||
|
||||
$results['total'] = count($orders);
|
||||
// Add date filter for incremental sync
|
||||
if ($since_timestamp) {
|
||||
$query_args['date_query'] = array(
|
||||
array(
|
||||
'column' => 'post_modified',
|
||||
'after' => date('Y-m-d H:i:s', $since_timestamp),
|
||||
'inclusive' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($results['total'] === 0) {
|
||||
// Get total count first
|
||||
$count_query = new WP_Query($query_args);
|
||||
$total_count = $count_query->found_posts;
|
||||
$results['total'] = $total_count;
|
||||
|
||||
if ($total_count === 0) {
|
||||
$results['message'] = 'No completed ticket orders found.';
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Get paginated orders
|
||||
$paginated_args = array_merge($query_args, array(
|
||||
'posts_per_page' => $limit,
|
||||
'offset' => $offset,
|
||||
'fields' => 'all',
|
||||
));
|
||||
$orders = get_posts($paginated_args);
|
||||
|
||||
// Calculate pagination
|
||||
$results['has_more'] = ($offset + count($orders)) < $total_count;
|
||||
$results['next_offset'] = $offset + count($orders);
|
||||
|
||||
// If staging mode, simulate the sync
|
||||
if ($this->is_staging) {
|
||||
$results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
|
||||
|
|
@ -336,9 +475,12 @@ class HVAC_Zoho_Sync {
|
|||
/**
|
||||
* Sync ticket attendees to Zoho Contacts + Campaign Members
|
||||
*
|
||||
* @param int $offset Starting offset for pagination
|
||||
* @param int $limit Number of records per batch
|
||||
* @param int|null $since_timestamp Optional timestamp to filter modified records
|
||||
* @return array Sync results
|
||||
*/
|
||||
public function sync_attendees() {
|
||||
public function sync_attendees($offset = 0, $limit = 50, $since_timestamp = null) {
|
||||
$results = array(
|
||||
'total' => 0,
|
||||
'synced' => 0,
|
||||
|
|
@ -346,27 +488,82 @@ class HVAC_Zoho_Sync {
|
|||
'errors' => array(),
|
||||
'staging_mode' => $this->is_staging,
|
||||
'contacts_created' => 0,
|
||||
'campaign_members_created' => 0
|
||||
'campaign_members_created' => 0,
|
||||
'responses' => array(),
|
||||
'debug_info' => HVAC_Zoho_CRM_Auth::get_debug_mode_info(),
|
||||
'version' => 'BATCH_V1',
|
||||
'has_more' => false,
|
||||
'next_offset' => 0,
|
||||
'batch_offset' => $offset,
|
||||
'incremental_sync' => !is_null($since_timestamp)
|
||||
);
|
||||
|
||||
// Get all ticket attendees (Tickets Commerce)
|
||||
$attendees = get_posts(array(
|
||||
// Build base query args
|
||||
$base_tc_args = array(
|
||||
'post_type' => 'tec_tc_attendee',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'any',
|
||||
));
|
||||
|
||||
// Also get PayPal attendees if any
|
||||
$tpp_attendees = get_posts(array(
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids'
|
||||
);
|
||||
$base_tpp_args = array(
|
||||
'post_type' => 'tribe_tpp_attendees',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'any',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids'
|
||||
);
|
||||
|
||||
// Add date filter for incremental sync
|
||||
if ($since_timestamp) {
|
||||
$date_query = array(
|
||||
array(
|
||||
'column' => 'post_date',
|
||||
'after' => date('Y-m-d H:i:s', $since_timestamp),
|
||||
'inclusive' => true,
|
||||
)
|
||||
);
|
||||
$base_tc_args['date_query'] = $date_query;
|
||||
$base_tpp_args['date_query'] = $date_query;
|
||||
}
|
||||
|
||||
// Get total count for all attendee types
|
||||
$tc_count_query = new WP_Query($base_tc_args);
|
||||
$tpp_count_query = new WP_Query($base_tpp_args);
|
||||
$total_count = $tc_count_query->found_posts + $tpp_count_query->found_posts;
|
||||
$results['total'] = $total_count;
|
||||
|
||||
if ($total_count === 0) {
|
||||
$results['message'] = 'No ticket attendees found.';
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Get paginated attendees - first from tec_tc_attendee
|
||||
$tc_paginated_args = array_merge($base_tc_args, array(
|
||||
'posts_per_page' => $limit,
|
||||
'offset' => $offset,
|
||||
'fields' => 'all',
|
||||
));
|
||||
$tc_attendees = get_posts($tc_paginated_args);
|
||||
|
||||
$all_attendees = array_merge($attendees, $tpp_attendees);
|
||||
$results['total'] = count($all_attendees);
|
||||
// If we need more to fill the batch, get from PayPal attendees
|
||||
$remaining = max(0, $limit - count($tc_attendees));
|
||||
$tpp_offset = max(0, $offset - $tc_count_query->found_posts);
|
||||
$tpp_attendees = array();
|
||||
if ($remaining > 0 && $tpp_offset >= 0) {
|
||||
$tpp_paginated_args = array_merge($base_tpp_args, array(
|
||||
'posts_per_page' => $remaining,
|
||||
'offset' => $tpp_offset,
|
||||
'fields' => 'all',
|
||||
));
|
||||
$tpp_attendees = get_posts($tpp_paginated_args);
|
||||
}
|
||||
|
||||
if ($results['total'] === 0) {
|
||||
$all_attendees = array_merge($tc_attendees, $tpp_attendees);
|
||||
|
||||
// Calculate pagination
|
||||
$results['has_more'] = ($offset + count($all_attendees)) < $total_count;
|
||||
$results['next_offset'] = $offset + count($all_attendees);
|
||||
|
||||
if (count($all_attendees) === 0) {
|
||||
$results['message'] = 'No ticket attendees found.';
|
||||
return $results;
|
||||
}
|
||||
|
|
@ -391,13 +588,21 @@ class HVAC_Zoho_Sync {
|
|||
return $results;
|
||||
}
|
||||
|
||||
$results['version'] = 'PRODUCTION_BATCH_V1';
|
||||
|
||||
// Wrap entire loop in try-catch for safety (catches PHP 7+ errors)
|
||||
try {
|
||||
foreach ($all_attendees as $attendee) {
|
||||
|
||||
try {
|
||||
$attendee_data = $this->prepare_attendee_data($attendee);
|
||||
|
||||
if (empty($attendee_data['email'])) {
|
||||
// Check for any usable email (fallback to mq_email if email is empty)
|
||||
$has_email = !empty($attendee_data['email']) || !empty($attendee_data['mq_email']);
|
||||
if (!$has_email) {
|
||||
$results['failed']++;
|
||||
$results['errors'][] = sprintf('Attendee %s: No email address', $attendee->ID);
|
||||
$processed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -407,12 +612,63 @@ class HVAC_Zoho_Sync {
|
|||
$results['contacts_created']++;
|
||||
}
|
||||
|
||||
if (count($results['responses']) < 5) {
|
||||
$cid_debug = $contact_id ? $contact_id : 'NULL';
|
||||
$results['responses'][] = array('type' => 'debug', 'msg' => "Debug: Attendee {$attendee->ID} found Contact ID: " . $cid_debug);
|
||||
}
|
||||
|
||||
// Step 2: Create Campaign Member (link Contact to Campaign)
|
||||
if ($contact_id && !empty($attendee_data['event_id'])) {
|
||||
$campaign_id = get_post_meta($attendee_data['event_id'], '_zoho_campaign_id', true);
|
||||
|
||||
// Debug: Log event_id and campaign_id for troubleshooting
|
||||
if (count($results['responses']) < 10) {
|
||||
$results['responses'][] = array(
|
||||
'type' => 'campaign_lookup',
|
||||
'attendee_id' => $attendee->ID,
|
||||
'event_id' => $attendee_data['event_id'],
|
||||
'campaign_id' => $campaign_id ?: 'NOT_SET'
|
||||
);
|
||||
}
|
||||
|
||||
if ($campaign_id) {
|
||||
$this->create_campaign_member($contact_id, $campaign_id, 'Attended');
|
||||
$assoc_response = $this->create_campaign_member($contact_id, $campaign_id, 'Attended');
|
||||
|
||||
// ALWAYS capture the first link attempt for debugging
|
||||
if (!isset($results['first_link_attempt'])) {
|
||||
$results['first_link_attempt'] = array(
|
||||
'attendee_id' => $attendee->ID,
|
||||
'contact_id' => $contact_id,
|
||||
'campaign_id' => $campaign_id,
|
||||
'response' => $assoc_response
|
||||
);
|
||||
}
|
||||
|
||||
// Debug: Add responses (increased limit to 20)
|
||||
if (count($results['responses']) < 20) {
|
||||
$results['responses'][] = array('type' => 'link_attempt', 'id' => $attendee->ID, 'response' => $assoc_response);
|
||||
}
|
||||
|
||||
if (isset($assoc_response['status']) && $assoc_response['status'] === 'success') {
|
||||
$results['campaign_members_created']++;
|
||||
} elseif (isset($assoc_response['data'][0]['status']) && $assoc_response['data'][0]['status'] === 'success') {
|
||||
$results['campaign_members_created']++;
|
||||
} else {
|
||||
$results['errors'][] = sprintf('Attendee %s: Failed to link to campaign. Response: %s', $attendee->ID, json_encode($assoc_response));
|
||||
if (isset($assoc_response['data'])) {
|
||||
$results['responses'][] = array('type' => 'link_error', 'id' => $attendee->ID, 'response' => $assoc_response);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$results['errors'][] = sprintf('Attendee %s: Event %s has no Zoho Campaign ID', $attendee->ID, $attendee_data['event_id']);
|
||||
}
|
||||
} elseif (!$contact_id) {
|
||||
$error_detail = $this->last_contact_error ?: 'Unknown error';
|
||||
$results['errors'][] = sprintf('Attendee %s: No contact_id created. %s', $attendee->ID, $error_detail);
|
||||
} elseif (empty($attendee_data['event_id'])) {
|
||||
// Debug: Log when event_id is missing
|
||||
if (count($results['responses']) < 10) {
|
||||
$results['responses'][] = array('type' => 'missing_event_id', 'attendee_id' => $attendee->ID);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -421,11 +677,16 @@ class HVAC_Zoho_Sync {
|
|||
// Update attendee meta with Zoho Contact ID
|
||||
update_post_meta($attendee->ID, '_zoho_contact_id', $contact_id);
|
||||
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// Catch both Exception and Error (PHP 7+)
|
||||
$results['failed']++;
|
||||
$results['errors'][] = sprintf('Attendee %s: %s', $attendee->ID, $e->getMessage());
|
||||
$results['errors'][] = sprintf('Attendee %s: [%s] %s', $attendee->ID, get_class($e), $e->getMessage());
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Outer catch for any loop-level errors
|
||||
$results['errors'][] = 'Loop error: [' . get_class($e) . '] ' . $e->getMessage();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
|
@ -433,9 +694,12 @@ class HVAC_Zoho_Sync {
|
|||
/**
|
||||
* Sync RSVPs to Zoho Leads + Campaign Members
|
||||
*
|
||||
* @param int $offset Starting offset for pagination
|
||||
* @param int $limit Number of records per batch
|
||||
* @param int|null $since_timestamp Optional timestamp to filter modified records
|
||||
* @return array Sync results
|
||||
*/
|
||||
public function sync_rsvps() {
|
||||
public function sync_rsvps($offset = 0, $limit = 50, $since_timestamp = null) {
|
||||
$results = array(
|
||||
'total' => 0,
|
||||
'synced' => 0,
|
||||
|
|
@ -443,27 +707,59 @@ class HVAC_Zoho_Sync {
|
|||
'errors' => array(),
|
||||
'staging_mode' => $this->is_staging,
|
||||
'leads_created' => 0,
|
||||
'campaign_members_created' => 0
|
||||
'campaign_members_created' => 0,
|
||||
'debug_info' => HVAC_Zoho_CRM_Auth::get_debug_mode_info(),
|
||||
'has_more' => false,
|
||||
'next_offset' => 0,
|
||||
'batch_offset' => $offset,
|
||||
'incremental_sync' => !is_null($since_timestamp)
|
||||
);
|
||||
|
||||
// Get RSVP attendees
|
||||
$rsvps = get_posts(array(
|
||||
// Build query args
|
||||
$query_args = array(
|
||||
'post_type' => 'tribe_rsvp_attendees',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'any',
|
||||
));
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids'
|
||||
);
|
||||
|
||||
$results['total'] = count($rsvps);
|
||||
// Add date filter for incremental sync
|
||||
if ($since_timestamp) {
|
||||
$query_args['date_query'] = array(
|
||||
array(
|
||||
'column' => 'post_date',
|
||||
'after' => date('Y-m-d H:i:s', $since_timestamp),
|
||||
'inclusive' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($results['total'] === 0) {
|
||||
// Get total count first
|
||||
$count_query = new WP_Query($query_args);
|
||||
$total_count = $count_query->found_posts;
|
||||
$results['total'] = $total_count;
|
||||
|
||||
if ($total_count === 0) {
|
||||
$results['message'] = 'No RSVP attendees found.';
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Get paginated RSVPs
|
||||
$paginated_args = array_merge($query_args, array(
|
||||
'posts_per_page' => $limit,
|
||||
'offset' => $offset,
|
||||
'fields' => 'all',
|
||||
));
|
||||
$rsvps = get_posts($paginated_args);
|
||||
|
||||
// Calculate pagination
|
||||
$results['has_more'] = ($offset + count($rsvps)) < $total_count;
|
||||
$results['next_offset'] = $offset + count($rsvps);
|
||||
|
||||
// If staging mode, simulate the sync
|
||||
if ($this->is_staging) {
|
||||
$results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
|
||||
$results['synced'] = $results['total'];
|
||||
$results['synced'] = count($rsvps);
|
||||
$results['test_data'] = array();
|
||||
|
||||
foreach ($rsvps as $rsvp) {
|
||||
|
|
@ -500,8 +796,19 @@ class HVAC_Zoho_Sync {
|
|||
if ($lead_id && !empty($rsvp_data['event_id'])) {
|
||||
$campaign_id = get_post_meta($rsvp_data['event_id'], '_zoho_campaign_id', true);
|
||||
if ($campaign_id) {
|
||||
$this->create_campaign_member($lead_id, $campaign_id, 'Responded', 'Leads');
|
||||
$assoc_response = $this->create_campaign_member($lead_id, $campaign_id, 'Responded', 'Leads');
|
||||
if (isset($assoc_response['status']) && $assoc_response['status'] === 'success') {
|
||||
$results['campaign_members_created']++;
|
||||
} elseif (isset($assoc_response['data'][0]['status']) && $assoc_response['data'][0]['status'] === 'success') {
|
||||
$results['campaign_members_created']++;
|
||||
} else {
|
||||
$results['errors'][] = sprintf('RSVP %s: Failed to link to campaign. Response: %s', $rsvp->ID, json_encode($assoc_response));
|
||||
if (isset($assoc_response['data'])) {
|
||||
$results['responses'][] = array('type' => 'link_error', 'id' => $rsvp->ID, 'response' => $assoc_response);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$results['errors'][] = sprintf('RSVP %s: Event %s has no Zoho Campaign ID', $rsvp->ID, $rsvp_data['event_id']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -520,32 +827,70 @@ class HVAC_Zoho_Sync {
|
|||
}
|
||||
|
||||
/**
|
||||
* Ensure a Contact exists in Zoho (create or get existing)
|
||||
* Ensure a Contact exists in Zoho (create or get existing, update fields)
|
||||
*
|
||||
* @param array $data Attendee data
|
||||
* @return string|null Zoho Contact ID
|
||||
*/
|
||||
private function ensure_contact_exists($data) {
|
||||
// Search for existing contact by email
|
||||
$search_response = $this->auth->make_api_request(
|
||||
'/Contacts/search?criteria=(Email:equals:' . urlencode($data['email']) . ')',
|
||||
'GET'
|
||||
);
|
||||
// Determine primary email for lookup
|
||||
// Prefer measureQuick email, fallback to attendee email if mq_email is empty
|
||||
// If both are empty, we can't create a contact
|
||||
$attendee_email = !empty($data['email']) ? $data['email'] : '';
|
||||
$mq_email = !empty($data['mq_email']) ? $data['mq_email'] : '';
|
||||
|
||||
if (!empty($search_response['data'])) {
|
||||
return $search_response['data'][0]['id'];
|
||||
// Use mq_email as primary if available, otherwise use attendee_email
|
||||
$primary_email = !empty($mq_email) ? $mq_email : $attendee_email;
|
||||
|
||||
// Set secondary email (only if different from primary and not empty)
|
||||
$other_email = '';
|
||||
if (!empty($mq_email) && !empty($attendee_email) && $mq_email !== $attendee_email) {
|
||||
$other_email = $attendee_email;
|
||||
}
|
||||
|
||||
// Create new contact
|
||||
// Build contact data for create or update
|
||||
$contact_data = array(
|
||||
'First_Name' => $data['first_name'] ?: 'Unknown',
|
||||
'Last_Name' => $data['last_name'] ?: 'Attendee',
|
||||
'Email' => $data['email'],
|
||||
'Email' => $primary_email,
|
||||
'Lead_Source' => 'Event Tickets',
|
||||
'Contact_Type' => 'Attendee',
|
||||
'WordPress_Attendee_ID' => $data['attendee_id'],
|
||||
);
|
||||
|
||||
// Add Other Email if we have both emails
|
||||
if (!empty($other_email)) {
|
||||
$contact_data['Secondary_Email'] = $other_email;
|
||||
}
|
||||
|
||||
// Add Mobile phone if available
|
||||
if (!empty($data['phone'])) {
|
||||
$contact_data['Mobile'] = $data['phone'];
|
||||
}
|
||||
|
||||
// Add Primary Role if available
|
||||
if (!empty($data['company_role'])) {
|
||||
$contact_data['Primary_Role'] = $data['company_role'];
|
||||
}
|
||||
|
||||
// Search for existing contact by primary email
|
||||
$search_response = $this->auth->make_api_request(
|
||||
'/Contacts/search?criteria=(Email:equals:' . urlencode($primary_email) . ')',
|
||||
'GET'
|
||||
);
|
||||
|
||||
if (!empty($search_response['data'])) {
|
||||
$contact_id = $search_response['data'][0]['id'];
|
||||
|
||||
// UPDATE existing contact with new field data
|
||||
$this->auth->make_api_request("/Contacts/{$contact_id}", 'PUT', array(
|
||||
'data' => array($contact_data)
|
||||
));
|
||||
|
||||
return $contact_id;
|
||||
}
|
||||
|
||||
// Create new contact
|
||||
$create_response = $this->auth->make_api_request('/Contacts', 'POST', array(
|
||||
'data' => array($contact_data)
|
||||
));
|
||||
|
|
@ -554,6 +899,26 @@ class HVAC_Zoho_Sync {
|
|||
return $create_response['data'][0]['details']['id'];
|
||||
}
|
||||
|
||||
// Handle DUPLICATE_DATA: Contact exists but with different email
|
||||
// Extract existing contact ID from the error response and use it
|
||||
if (!empty($create_response['data'][0]['code']) &&
|
||||
$create_response['data'][0]['code'] === 'DUPLICATE_DATA' &&
|
||||
!empty($create_response['data'][0]['details']['duplicate_record']['id'])) {
|
||||
|
||||
$existing_id = $create_response['data'][0]['details']['duplicate_record']['id'];
|
||||
|
||||
// Update the existing contact with new data
|
||||
$this->auth->make_api_request("/Contacts/{$existing_id}", 'PUT', array(
|
||||
'data' => array($contact_data)
|
||||
));
|
||||
|
||||
return $existing_id;
|
||||
}
|
||||
|
||||
// Store error for debugging and log it
|
||||
$this->last_contact_error = "Email: {$primary_email}, Response: " . json_encode($create_response);
|
||||
$this->auth->log_debug("Failed to create contact: " . $this->last_contact_error);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -604,13 +969,26 @@ class HVAC_Zoho_Sync {
|
|||
* @param string $type 'Contacts' or 'Leads'
|
||||
*/
|
||||
private function create_campaign_member($record_id, $campaign_id, $status = 'Attended', $type = 'Contacts') {
|
||||
$endpoint = "/Campaigns/{$campaign_id}/{$type}/{$record_id}";
|
||||
// v6 API: Associate Contact/Lead with Campaign via related list
|
||||
// PUT /{Contacts|Leads}/{record_id}/Campaigns
|
||||
// The Contact/Lead is the parent, Campaigns is the related list
|
||||
// Campaign ID goes in the request body, not the URL
|
||||
|
||||
$this->auth->make_api_request($endpoint, 'PUT', array(
|
||||
$endpoint = "/{$type}/{$record_id}/Campaigns";
|
||||
|
||||
// Debug: Log the association attempt
|
||||
$this->auth->log_debug("Attempting to associate {$type} {$record_id} with Campaign {$campaign_id} via {$endpoint}");
|
||||
|
||||
$response = $this->auth->make_api_request($endpoint, 'PUT', array(
|
||||
'data' => array(
|
||||
array('Member_Status' => $status)
|
||||
array(
|
||||
'id' => $campaign_id,
|
||||
'Member_Status' => $status
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -622,22 +1000,112 @@ class HVAC_Zoho_Sync {
|
|||
private function prepare_campaign_data($event) {
|
||||
$trainer_id = get_post_meta($event->ID, '_trainer_id', true);
|
||||
$trainer = get_user_by('id', $trainer_id);
|
||||
$venue = tribe_get_venue($event->ID);
|
||||
$venue_id = tribe_get_venue_id($event->ID);
|
||||
|
||||
return array(
|
||||
'Campaign_Name' => get_the_title($event->ID),
|
||||
// Get venue details
|
||||
$venue_name = '';
|
||||
$venue_city = '';
|
||||
$venue_state = '';
|
||||
$venue_details = '';
|
||||
|
||||
if ($venue_id) {
|
||||
$venue_name = html_entity_decode(get_the_title($venue_id), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$venue_city = tribe_get_city($event->ID);
|
||||
$venue_state = tribe_get_state($event->ID);
|
||||
$venue_address = tribe_get_address($event->ID);
|
||||
|
||||
// Build venue details string
|
||||
$venue_parts = array_filter(array($venue_name, $venue_address, $venue_city, $venue_state));
|
||||
$venue_details = implode(', ', $venue_parts);
|
||||
}
|
||||
|
||||
// Sanitize description: strip tags and limit length, then decode entities
|
||||
$description = wp_strip_all_tags(get_the_content(null, false, $event));
|
||||
$description = html_entity_decode($description, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
if (strlen($description) > 30000) {
|
||||
$description = substr($description, 0, 30000) . '...';
|
||||
}
|
||||
|
||||
$campaign_name = html_entity_decode(get_the_title($event->ID), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$trainer_name = $trainer ? html_entity_decode($trainer->display_name, ENT_QUOTES | ENT_HTML5, 'UTF-8') : '';
|
||||
|
||||
// Get trainer's Zoho Contact ID (if they've been synced)
|
||||
$instructor_zoho_id = '';
|
||||
if ($trainer) {
|
||||
$instructor_zoho_id = get_user_meta($trainer->ID, '_zoho_contact_id', true);
|
||||
}
|
||||
|
||||
// Calculate ticket counts
|
||||
$total_capacity = intval(get_post_meta($event->ID, '_stock', true));
|
||||
$tickets_sold = $this->get_attendee_count($event->ID);
|
||||
$tickets_available = max(0, $total_capacity - $tickets_sold);
|
||||
|
||||
// Build campaign data array
|
||||
$data = array(
|
||||
'Campaign_Name' => substr($campaign_name, 0, 200),
|
||||
'Start_Date' => tribe_get_start_date($event->ID, false, 'Y-m-d'),
|
||||
'End_Date' => tribe_get_end_date($event->ID, false, 'Y-m-d'),
|
||||
'Status' => (tribe_get_end_date($event->ID, false, 'U') < time()) ? 'Completed' : 'Active',
|
||||
'Description' => get_the_content(null, false, $event),
|
||||
'Description' => $description,
|
||||
'Type' => 'Training Event',
|
||||
'Expected_Revenue' => floatval(get_post_meta($event->ID, '_price', true)),
|
||||
'Total_Capacity' => intval(get_post_meta($event->ID, '_stock', true)),
|
||||
'Venue' => $venue ? get_the_title($venue) : '',
|
||||
'Trainer_Name' => $trainer ? $trainer->display_name : '',
|
||||
'Trainer_Email' => $trainer ? $trainer->user_email : '',
|
||||
'WordPress_Event_ID' => $event->ID
|
||||
'Total_Capacity' => $total_capacity,
|
||||
'Tickets_Sold' => $tickets_sold,
|
||||
'Tickets_Available' => $tickets_available,
|
||||
'Event_City' => substr($venue_city, 0, 100),
|
||||
'Event_State' => substr($venue_state, 0, 100),
|
||||
'Event_URL' => get_permalink($event->ID),
|
||||
'Venue_Details' => substr($venue_details, 0, 250),
|
||||
'Instructor_Email' => $trainer ? $trainer->user_email : '',
|
||||
);
|
||||
|
||||
// Add Instructor lookup only if we have a valid Zoho Contact ID
|
||||
if (!empty($instructor_zoho_id)) {
|
||||
$data['Instructor'] = $instructor_zoho_id;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of attendees for an event
|
||||
*
|
||||
* @param int $event_id Event post ID
|
||||
* @return int Attendee count
|
||||
*/
|
||||
private function get_attendee_count($event_id) {
|
||||
// Check Tickets Commerce attendees
|
||||
$tc_attendees = get_posts(array(
|
||||
'post_type' => 'tec_tc_attendee',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'any',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_tec_tickets_commerce_event',
|
||||
'value' => $event_id,
|
||||
'compare' => '='
|
||||
)
|
||||
),
|
||||
'fields' => 'ids'
|
||||
));
|
||||
|
||||
// Also check PayPal attendees
|
||||
$tpp_attendees = get_posts(array(
|
||||
'post_type' => 'tribe_tpp_attendees',
|
||||
'posts_per_page' => -1,
|
||||
'post_status' => 'any',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_tribe_tpp_event',
|
||||
'value' => $event_id,
|
||||
'compare' => '='
|
||||
)
|
||||
),
|
||||
'fields' => 'ids'
|
||||
));
|
||||
|
||||
return count($tc_attendees) + count($tpp_attendees);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -657,12 +1125,12 @@ class HVAC_Zoho_Sync {
|
|||
}
|
||||
|
||||
return array(
|
||||
'First_Name' => get_user_meta($user->ID, 'first_name', true),
|
||||
'Last_Name' => get_user_meta($user->ID, 'last_name', true),
|
||||
'First_Name' => html_entity_decode(get_user_meta($user->ID, 'first_name', true), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'Last_Name' => html_entity_decode(get_user_meta($user->ID, 'last_name', true), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'Email' => $user->user_email,
|
||||
'Phone' => get_user_meta($user->ID, 'phone_number', true),
|
||||
'Title' => get_user_meta($user->ID, 'hvac_professional_title', true),
|
||||
'Company' => get_user_meta($user->ID, 'hvac_company_name', true),
|
||||
'Title' => html_entity_decode(get_user_meta($user->ID, 'hvac_professional_title', true), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'Company' => html_entity_decode(get_user_meta($user->ID, 'hvac_company_name', true), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'Lead_Source' => 'HVAC Community Events',
|
||||
'Contact_Type' => $role,
|
||||
'WordPress_User_ID' => $user->ID,
|
||||
|
|
@ -746,6 +1214,21 @@ class HVAC_Zoho_Sync {
|
|||
$full_name = get_post_meta($attendee->ID, '_tec_tickets_commerce_full_name', true);
|
||||
$email = get_post_meta($attendee->ID, '_tec_tickets_commerce_email', true);
|
||||
$checkin = get_post_meta($attendee->ID, '_tec_tickets_commerce_checked_in', true);
|
||||
|
||||
// Custom attendee fields stored in _tec_tickets_commerce_attendee_fields (serialized array)
|
||||
// Field names use hyphens: attendee-cell-phone, company-role, measurequick-email
|
||||
$attendee_fields = get_post_meta($attendee->ID, '_tec_tickets_commerce_attendee_fields', true);
|
||||
|
||||
$phone = '';
|
||||
$company_role = '';
|
||||
$mq_email = '';
|
||||
|
||||
if (is_array($attendee_fields)) {
|
||||
$phone = isset($attendee_fields['attendee-cell-phone']) ? $attendee_fields['attendee-cell-phone'] : '';
|
||||
$company_role = isset($attendee_fields['company-role']) ? $attendee_fields['company-role'] : '';
|
||||
$mq_email = isset($attendee_fields['measurequick-email']) ? $attendee_fields['measurequick-email'] : '';
|
||||
}
|
||||
|
||||
} else {
|
||||
// PayPal or other attendee types (tribe_tpp_attendees)
|
||||
$event_id = get_post_meta($attendee->ID, '_tribe_tpp_event', true);
|
||||
|
|
@ -754,6 +1237,12 @@ class HVAC_Zoho_Sync {
|
|||
$full_name = get_post_meta($attendee->ID, '_tribe_tickets_full_name', true);
|
||||
$email = get_post_meta($attendee->ID, '_tribe_tickets_email', true);
|
||||
$checkin = get_post_meta($attendee->ID, '_tribe_tpp_checkin', true);
|
||||
|
||||
// Legacy attendee meta for custom fields (hyphenated keys)
|
||||
$attendee_meta = get_post_meta($attendee->ID, '_tribe_tickets_meta', true);
|
||||
$phone = is_array($attendee_meta) && isset($attendee_meta['attendee-cell-phone']) ? $attendee_meta['attendee-cell-phone'] : '';
|
||||
$company_role = is_array($attendee_meta) && isset($attendee_meta['company-role']) ? $attendee_meta['company-role'] : '';
|
||||
$mq_email = is_array($attendee_meta) && isset($attendee_meta['measurequick-email']) ? $attendee_meta['measurequick-email'] : '';
|
||||
}
|
||||
|
||||
// Parse full name into first/last
|
||||
|
|
@ -776,6 +1265,9 @@ class HVAC_Zoho_Sync {
|
|||
'first_name' => $first_name,
|
||||
'last_name' => $last_name,
|
||||
'email' => $email,
|
||||
'phone' => $phone,
|
||||
'company_role' => $company_role,
|
||||
'mq_email' => $mq_email,
|
||||
'checked_in' => !empty($checkin),
|
||||
'zoho_contact' => array(
|
||||
'First_Name' => $first_name ?: 'Unknown',
|
||||
|
|
|
|||
Loading…
Reference in a new issue