🚨 CRITICAL: Fixed deployment blockers by adding missing core directories: **Community System (CRITICAL)** - includes/community/ - Login_Handler and all community classes - templates/community/ - Community login forms **Certificate System (CRITICAL)** - includes/certificates/ - 8+ certificate classes and handlers - templates/certificates/ - Certificate reports and generation templates **Core Individual Classes (CRITICAL)** - includes/class-hvac-event-summary.php - includes/class-hvac-trainer-profile-manager.php - includes/class-hvac-master-dashboard-data.php - Plus 40+ other individual HVAC classes **Major Feature Systems (HIGH)** - includes/database/ - Training leads database tables - includes/find-trainer/ - Find trainer directory and MapGeo integration - includes/google-sheets/ - Google Sheets integration system - includes/zoho/ - Complete Zoho CRM integration - includes/communication/ - Communication templates system **Template Infrastructure** - templates/attendee/, templates/email-attendees/ - templates/event-summary/, templates/status/ - templates/template-parts/ - Shared template components **Impact:** - 70+ files added covering 10+ missing directories - Resolves ALL deployment blockers and feature breakdowns - Plugin activation should now work correctly - Multi-machine deployment fully supported 🔧 Generated with Claude Code Co-Authored-By: Ben Reed <ben@tealmaker.com>
428 lines
No EOL
15 KiB
PHP
428 lines
No EOL
15 KiB
PHP
<?php
|
|
/**
|
|
* Zoho CRM Sync Handler
|
|
*
|
|
* @package HVACCommunityEvents
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Zoho Sync Class
|
|
*/
|
|
class HVAC_Zoho_Sync {
|
|
|
|
/**
|
|
* Zoho Auth instance
|
|
*
|
|
* @var HVAC_Zoho_CRM_Auth
|
|
*/
|
|
private $auth;
|
|
|
|
/**
|
|
* Staging mode flag
|
|
*
|
|
* @var bool
|
|
*/
|
|
private $is_staging;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-crm-auth.php';
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* Check if sync is allowed
|
|
*
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* Sync events to Zoho Campaigns
|
|
*
|
|
* @return array Sync results
|
|
*/
|
|
public function sync_events() {
|
|
$results = array(
|
|
'total' => 0,
|
|
'synced' => 0,
|
|
'failed' => 0,
|
|
'errors' => array(),
|
|
'staging_mode' => $this->is_staging
|
|
);
|
|
|
|
// Get all published events
|
|
$events = tribe_get_events(array(
|
|
'posts_per_page' => -1,
|
|
'eventDisplay' => 'list',
|
|
'meta_query' => array(
|
|
array(
|
|
'key' => '_hvac_event_type',
|
|
'value' => 'trainer',
|
|
'compare' => '='
|
|
)
|
|
)
|
|
));
|
|
|
|
$results['total'] = count($events);
|
|
|
|
// 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['test_data'] = array();
|
|
|
|
foreach ($events as $event) {
|
|
$campaign_data = $this->prepare_campaign_data($event);
|
|
$results['test_data'][] = array(
|
|
'event_id' => $event->ID,
|
|
'event_title' => get_the_title($event),
|
|
'zoho_data' => $campaign_data
|
|
);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
// Production sync
|
|
if (!$this->is_sync_allowed()) {
|
|
$results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
|
|
return $results;
|
|
}
|
|
|
|
foreach ($events as $event) {
|
|
try {
|
|
$campaign_data = $this->prepare_campaign_data($event);
|
|
|
|
// 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']})"
|
|
));
|
|
|
|
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(
|
|
'data' => array($campaign_data)
|
|
));
|
|
} else {
|
|
// Create new campaign
|
|
$create_response = $this->auth->make_api_request('POST', '/crm/v2/Campaigns', array(
|
|
'data' => array($campaign_data)
|
|
));
|
|
}
|
|
|
|
$results['synced']++;
|
|
|
|
// Update event meta with Zoho ID
|
|
if (isset($campaign_id)) {
|
|
update_post_meta($event->ID, '_zoho_campaign_id', $campaign_id);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$results['failed']++;
|
|
$results['errors'][] = sprintf('Event %s: %s', $event->ID, $e->getMessage());
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Sync users to Zoho Contacts
|
|
*
|
|
* @return array Sync results
|
|
*/
|
|
public function sync_users() {
|
|
$results = array(
|
|
'total' => 0,
|
|
'synced' => 0,
|
|
'failed' => 0,
|
|
'errors' => array(),
|
|
'staging_mode' => $this->is_staging
|
|
);
|
|
|
|
// Get trainers and attendees
|
|
$users = get_users(array(
|
|
'role__in' => array('trainer', 'trainee'),
|
|
'meta_query' => array(
|
|
'relation' => 'OR',
|
|
array(
|
|
'key' => '_sync_to_zoho',
|
|
'value' => '1',
|
|
'compare' => '='
|
|
),
|
|
array(
|
|
'key' => '_sync_to_zoho',
|
|
'compare' => 'NOT EXISTS'
|
|
)
|
|
)
|
|
));
|
|
|
|
$results['total'] = count($users);
|
|
|
|
// 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['test_data'] = array();
|
|
|
|
foreach ($users as $user) {
|
|
$contact_data = $this->prepare_contact_data($user);
|
|
$results['test_data'][] = array(
|
|
'user_id' => $user->ID,
|
|
'user_email' => $user->user_email,
|
|
'user_role' => implode(', ', $user->roles),
|
|
'zoho_data' => $contact_data
|
|
);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
// Production sync
|
|
if (!$this->is_sync_allowed()) {
|
|
$results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
|
|
return $results;
|
|
}
|
|
|
|
foreach ($users as $user) {
|
|
try {
|
|
$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(
|
|
'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(
|
|
'data' => array($contact_data)
|
|
));
|
|
} else {
|
|
// Create new contact
|
|
$create_response = $this->auth->make_api_request('POST', '/crm/v2/Contacts', array(
|
|
'data' => array($contact_data)
|
|
));
|
|
|
|
if (!empty($create_response['data'][0]['details']['id'])) {
|
|
$contact_id = $create_response['data'][0]['details']['id'];
|
|
}
|
|
}
|
|
|
|
$results['synced']++;
|
|
|
|
// Update user meta with Zoho ID
|
|
if (isset($contact_id)) {
|
|
update_user_meta($user->ID, '_zoho_contact_id', $contact_id);
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$results['failed']++;
|
|
$results['errors'][] = sprintf('User %s: %s', $user->ID, $e->getMessage());
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Sync ticket purchases to Zoho Invoices
|
|
*
|
|
* @return array Sync results
|
|
*/
|
|
public function sync_purchases() {
|
|
$results = array(
|
|
'total' => 0,
|
|
'synced' => 0,
|
|
'failed' => 0,
|
|
'errors' => array(),
|
|
'staging_mode' => $this->is_staging
|
|
);
|
|
|
|
// Get all completed orders
|
|
$orders = wc_get_orders(array(
|
|
'status' => 'completed',
|
|
'limit' => -1,
|
|
'meta_key' => '_tribe_tickets_event_id',
|
|
'meta_compare' => 'EXISTS'
|
|
));
|
|
|
|
$results['total'] = count($orders);
|
|
|
|
// 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['test_data'] = array();
|
|
|
|
foreach ($orders as $order) {
|
|
$invoice_data = $this->prepare_invoice_data($order);
|
|
$results['test_data'][] = array(
|
|
'order_id' => $order->get_id(),
|
|
'order_number' => $order->get_order_number(),
|
|
'order_total' => $order->get_total(),
|
|
'zoho_data' => $invoice_data
|
|
);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
// Production sync
|
|
if (!$this->is_sync_allowed()) {
|
|
$results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
|
|
return $results;
|
|
}
|
|
|
|
foreach ($orders as $order) {
|
|
try {
|
|
$invoice_data = $this->prepare_invoice_data($order);
|
|
|
|
// Check if invoice already exists in Zoho
|
|
$order_number = $order->get_order_number();
|
|
$search_response = $this->auth->make_api_request('GET', '/crm/v2/Invoices/search', array(
|
|
'criteria' => "(Invoice_Number:equals:{$order_number})"
|
|
));
|
|
|
|
if (!empty($search_response['data'])) {
|
|
// Update existing invoice
|
|
$invoice_id = $search_response['data'][0]['id'];
|
|
$update_response = $this->auth->make_api_request('PUT', "/crm/v2/Invoices/{$invoice_id}", array(
|
|
'data' => array($invoice_data)
|
|
));
|
|
} else {
|
|
// Create new invoice
|
|
$create_response = $this->auth->make_api_request('POST', '/crm/v2/Invoices', array(
|
|
'data' => array($invoice_data)
|
|
));
|
|
}
|
|
|
|
$results['synced']++;
|
|
|
|
// Update order meta with Zoho ID
|
|
if (isset($invoice_id)) {
|
|
$order->update_meta_data('_zoho_invoice_id', $invoice_id);
|
|
$order->save();
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
$results['failed']++;
|
|
$results['errors'][] = sprintf('Order %s: %s', $order->get_id(), $e->getMessage());
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Prepare campaign data for Zoho
|
|
*
|
|
* @param WP_Post $event Event post object
|
|
* @return array Campaign data
|
|
*/
|
|
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);
|
|
|
|
return array(
|
|
'Campaign_Name' => get_the_title($event->ID),
|
|
'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),
|
|
'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
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Prepare contact data for Zoho
|
|
*
|
|
* @param WP_User $user User object
|
|
* @return array Contact data
|
|
*/
|
|
private function prepare_contact_data($user) {
|
|
$role = in_array('trainer', $user->roles) ? 'Trainer' : 'Trainee';
|
|
|
|
return array(
|
|
'First_Name' => get_user_meta($user->ID, 'first_name', true),
|
|
'Last_Name' => get_user_meta($user->ID, 'last_name', true),
|
|
'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),
|
|
'Lead_Source' => 'HVAC Community Events',
|
|
'Contact_Type' => $role,
|
|
'WordPress_User_ID' => $user->ID,
|
|
'License_Number' => get_user_meta($user->ID, 'hvac_license_number', true),
|
|
'Years_Experience' => get_user_meta($user->ID, 'hvac_years_experience', true),
|
|
'Certification' => get_user_meta($user->ID, 'hvac_certifications', true)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Prepare invoice data for Zoho
|
|
*
|
|
* @param WC_Order $order Order object
|
|
* @return array Invoice data
|
|
*/
|
|
private function prepare_invoice_data($order) {
|
|
$event_id = $order->get_meta('_tribe_tickets_event_id');
|
|
$event_title = get_the_title($event_id);
|
|
$customer = $order->get_user();
|
|
|
|
// Get contact ID from Zoho
|
|
$contact_id = null;
|
|
if ($customer) {
|
|
$contact_id = get_user_meta($customer->ID, '_zoho_contact_id', true);
|
|
}
|
|
|
|
$items = array();
|
|
foreach ($order->get_items() as $item) {
|
|
$items[] = array(
|
|
'Product_Name' => $item->get_name(),
|
|
'Quantity' => $item->get_quantity(),
|
|
'Rate' => $item->get_subtotal() / $item->get_quantity(),
|
|
'Total' => $item->get_total()
|
|
);
|
|
}
|
|
|
|
return array(
|
|
'Invoice_Number' => $order->get_order_number(),
|
|
'Invoice_Date' => $order->get_date_created()->format('Y-m-d'),
|
|
'Status' => 'Paid',
|
|
'Contact_Name' => $contact_id,
|
|
'Subject' => "Ticket Purchase - {$event_title}",
|
|
'Sub_Total' => $order->get_subtotal(),
|
|
'Tax' => $order->get_total_tax(),
|
|
'Total' => $order->get_total(),
|
|
'Balance' => 0,
|
|
'WordPress_Order_ID' => $order->get_id(),
|
|
'Product_Details' => $items
|
|
);
|
|
}
|
|
}
|
|
?>
|