diff --git a/.gitignore b/.gitignore
index d170683a..ce7896f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,19 @@
!/scripts/
/scripts/*
!/scripts/*.sh
+
+# Plugin files
+!hvac-community-events.php
+!/includes/
+/includes/*
+!/includes/**/*.php
+!/templates/
+/templates/*
+!/templates/**/*.php
+!/assets/
+/assets/*
+!/assets/css/*.css
+!/assets/js/*.js
!/wordpress-dev/tests/
/wordpress-dev/tests/*
!/wordpress-dev/tests/e2e/
diff --git a/hvac-community-events.php b/hvac-community-events.php
new file mode 100644
index 00000000..6cafcf5d
--- /dev/null
+++ b/hvac-community-events.php
@@ -0,0 +1,1029 @@
+init_secure_download();
+ HVAC_Logger::info('Certificate security initialized during activation', 'Activation');
+ }
+
+ // Define hierarchical page structure
+ $parent_pages = [
+ 'trainer' => [
+ 'title' => 'Trainer',
+ 'content' => '',
+ 'children' => [
+ 'dashboard' => [
+ 'title' => 'Trainer Dashboard',
+ 'content' => '[hvac_dashboard]',
+ ],
+ 'registration' => [
+ 'title' => 'Trainer Registration',
+ 'content' => '[hvac_trainer_registration]',
+ ],
+ 'my-profile' => [
+ 'title' => 'Trainer Profile',
+ 'content' => '[hvac_trainer_profile]',
+ ],
+ 'email-attendees' => [
+ 'title' => 'Email Attendees',
+ 'content' => '[hvac_email_attendees]',
+ ],
+ 'certificate-reports' => [
+ 'title' => 'Certificate Reports',
+ 'content' => '[hvac_certificate_reports]',
+ ],
+ 'generate-certificates' => [
+ 'title' => 'Generate Certificates',
+ 'content' => '[hvac_generate_certificates]',
+ ],
+ 'documentation' => [
+ 'title' => 'Trainer Documentation',
+ 'content' => '[hvac_documentation]',
+ ],
+ 'attendee-profile' => [
+ 'title' => 'Attendee Profile',
+ 'content' => '[hvac_attendee_profile]',
+ ],
+ 'communication-templates' => [
+ 'title' => 'Communication Templates',
+ 'content' => '[hvac_communication_templates]',
+ ],
+ 'communication-schedules' => [
+ 'title' => 'Communication Schedules',
+ 'content' => '[hvac_communication_schedules]',
+ ],
+ 'event' => [
+ 'title' => 'Event',
+ 'content' => '',
+ 'children' => [
+ 'manage' => [
+ 'title' => 'Manage Event',
+ 'content' => '
+
+
+
+
+
+ [tribe_community_events view="submission_form"]',
+ ],
+ 'summary' => [
+ 'title' => 'Event Summary',
+ 'content' => '[hvac_event_summary]',
+ ],
+ ]
+ ]
+ ]
+ ],
+ 'master-trainer' => [
+ 'title' => 'Master Trainer',
+ 'content' => '',
+ 'children' => [
+ 'dashboard' => [
+ 'title' => 'Master Dashboard',
+ 'content' => '[hvac_master_dashboard]',
+ ],
+ 'certificate-fix' => [
+ 'title' => 'Certificate System Diagnostics',
+ 'content' => '[hvac_certificate_fix]',
+ ],
+ 'google-sheets' => [
+ 'title' => 'Google Sheets Integration',
+ 'content' => '[hvac_google_sheets]',
+ ],
+ ]
+ ]
+ ];
+
+ // Define root pages (flat structure)
+ $root_pages = [
+ 'training-login' => [
+ 'title' => 'Trainer Login',
+ 'content' => '[hvac_community_login]',
+ 'template' => 'page-community-login.php',
+ ],
+ 'registration-pending' => [
+ 'title' => 'Registration Pending',
+ 'content' => '
+
Registration Submitted Successfully
+
Thank you for registering as an HVAC trainer. Your registration has been submitted and is currently being reviewed.
+
You will receive an email notification once your account has been approved and activated.
+
If you have any questions, please contact our support team.
+
Return to Login
+
',
+ ],
+ ];
+
+ $created_pages = [];
+
+ // Create root pages first
+ HVAC_Logger::info('Creating root pages...', 'Activation');
+ foreach ($root_pages as $slug => $page_data) {
+ $existing = get_page_by_path($slug);
+ if (!$existing) {
+ $page_args = [
+ 'post_title' => $page_data['title'],
+ 'post_name' => $slug,
+ 'post_content' => $page_data['content'],
+ 'post_status' => 'publish',
+ 'post_type' => 'page',
+ 'comment_status' => 'closed',
+ 'ping_status' => 'closed',
+ ];
+
+ if (!empty($page_data['template'])) {
+ $page_args['page_template'] = $page_data['template'];
+ }
+
+ $page_id = wp_insert_post($page_args);
+ if (!is_wp_error($page_id)) {
+ $created_pages[$slug] = $page_id;
+ HVAC_Logger::info("Created root page: {$page_data['title']} (/{$slug}/)", 'Activation');
+ } else {
+ HVAC_Logger::error("Failed to create root page: {$slug} - " . $page_id->get_error_message(), 'Activation');
+ }
+ } else {
+ HVAC_Logger::info("Root page exists: {$page_data['title']} (/{$slug}/)", 'Activation');
+ }
+ }
+
+ // Create hierarchical pages
+ HVAC_Logger::info('Creating hierarchical pages...', 'Activation');
+ foreach ($parent_pages as $parent_slug => $parent_data) {
+ // Create parent page
+ $existing_parent = get_page_by_path($parent_slug);
+ $parent_id = null;
+
+ if (!$existing_parent) {
+ $parent_args = [
+ 'post_title' => $parent_data['title'],
+ 'post_name' => $parent_slug,
+ 'post_content' => $parent_data['content'],
+ 'post_status' => 'publish',
+ 'post_type' => 'page',
+ 'comment_status' => 'closed',
+ 'ping_status' => 'closed',
+ ];
+
+ $parent_id = wp_insert_post($parent_args);
+ if (!is_wp_error($parent_id)) {
+ $created_pages[$parent_slug] = $parent_id;
+ HVAC_Logger::info("Created parent page: {$parent_data['title']} (/{$parent_slug}/)", 'Activation');
+ } else {
+ HVAC_Logger::error("Failed to create parent page: {$parent_slug} - " . $parent_id->get_error_message(), 'Activation');
+ continue;
+ }
+ } else {
+ $parent_id = $existing_parent->ID;
+ HVAC_Logger::info("Parent page exists: {$parent_data['title']} (/{$parent_slug}/)", 'Activation');
+ }
+
+ // Create child pages
+ if ($parent_id && isset($parent_data['children'])) {
+ foreach ($parent_data['children'] as $child_slug => $child_data) {
+ $full_path = $parent_slug . '/' . $child_slug;
+ $existing_child = get_page_by_path($full_path);
+
+ if (!$existing_child) {
+ $child_args = [
+ 'post_title' => $child_data['title'],
+ 'post_name' => $child_slug,
+ 'post_content' => $child_data['content'],
+ 'post_status' => 'publish',
+ 'post_type' => 'page',
+ 'post_parent' => $parent_id,
+ 'comment_status' => 'closed',
+ 'ping_status' => 'closed',
+ ];
+
+ $child_id = wp_insert_post($child_args);
+ if (!is_wp_error($child_id)) {
+ $created_pages[$full_path] = $child_id;
+ HVAC_Logger::info("Created child page: {$child_data['title']} (/{$full_path}/)", 'Activation');
+
+ // Handle grandchildren (like event/manage, event/summary)
+ if (isset($child_data['children'])) {
+ foreach ($child_data['children'] as $grandchild_slug => $grandchild_data) {
+ $grandchild_path = $parent_slug . '/' . $child_slug . '/' . $grandchild_slug;
+ $existing_grandchild = get_page_by_path($grandchild_path);
+
+ if (!$existing_grandchild) {
+ $grandchild_args = [
+ 'post_title' => $grandchild_data['title'],
+ 'post_name' => $grandchild_slug,
+ 'post_content' => $grandchild_data['content'],
+ 'post_status' => 'publish',
+ 'post_type' => 'page',
+ 'post_parent' => $child_id,
+ 'comment_status' => 'closed',
+ 'ping_status' => 'closed',
+ ];
+
+ $grandchild_id = wp_insert_post($grandchild_args);
+ if (!is_wp_error($grandchild_id)) {
+ $created_pages[$grandchild_path] = $grandchild_id;
+ HVAC_Logger::info("Created grandchild page: {$grandchild_data['title']} (/{$grandchild_path}/)", 'Activation');
+ } else {
+ HVAC_Logger::error("Failed to create grandchild page: {$grandchild_path} - " . $grandchild_id->get_error_message(), 'Activation');
+ }
+ } else {
+ HVAC_Logger::info("Grandchild page exists: {$grandchild_data['title']} (/{$grandchild_path}/)", 'Activation');
+ }
+ }
+ }
+ } else {
+ HVAC_Logger::error("Failed to create child page: {$full_path} - " . $child_id->get_error_message(), 'Activation');
+ }
+ } else {
+ HVAC_Logger::info("Child page exists: {$child_data['title']} (/{$full_path}/)", 'Activation');
+ }
+ }
+ }
+ }
+
+ // Store created pages in WordPress option
+ update_option('hvac_ce_created_pages', $created_pages);
+ HVAC_Logger::info('Page creation completed. Created ' . count($created_pages) . ' pages', 'Activation');
+
+ // Create the custom roles
+ $roles_manager = new HVAC_Roles();
+
+ // Create trainer role
+ $result = $roles_manager->create_trainer_role();
+ if ($result) {
+ HVAC_Logger::info('Successfully created hvac_trainer role.', 'Activation');
+ } else {
+ HVAC_Logger::error('Failed to create hvac_trainer role.', 'Activation');
+ }
+
+ // Create master trainer role
+ $master_result = $roles_manager->create_master_trainer_role();
+ if ($master_result) {
+ HVAC_Logger::info('Successfully created hvac_master_trainer role.', 'Activation');
+ } else {
+ HVAC_Logger::error('Failed to create hvac_master_trainer role.', 'Activation');
+ }
+
+ // Grant administrators access to dashboard to prevent redirect loops
+ $admin_access = $roles_manager->grant_admin_dashboard_access();
+ if ($admin_access) {
+ HVAC_Logger::info('Successfully granted admin dashboard access.', 'Activation');
+ } else {
+ HVAC_Logger::error('Failed to grant admin dashboard access.', 'Activation');
+ }
+
+ // Flush rewrite rules to ensure new URLs work
+ flush_rewrite_rules();
+ HVAC_Logger::info('Rewrite rules flushed', 'Activation');
+
+ HVAC_Logger::info('Completed hierarchical page creation and role setup process', 'Activation');
+
+} // End hvac_ce_create_required_pages
+register_activation_hook(__FILE__, 'hvac_ce_create_required_pages');
+
+/**
+ * Handle backward compatibility redirects for old URLs.
+ *
+ * This function redirects old page URLs to their new hierarchical structure
+ * to maintain compatibility for existing bookmarks and external links.
+ */
+function hvac_ce_handle_legacy_redirects() {
+ // Legacy URL to new URL mapping
+ $legacy_redirects = [
+ 'community-login' => 'training-login',
+ 'hvac-dashboard' => 'trainer/dashboard',
+ 'master-dashboard' => 'master-trainer/dashboard',
+ 'manage-event' => 'trainer/event/manage',
+ 'trainer-profile' => 'trainer/my-profile',
+ 'event-summary' => 'trainer/event/summary',
+ 'email-attendees' => 'trainer/email-attendees',
+ 'certificate-reports' => 'trainer/certificate-reports',
+ 'generate-certificates' => 'trainer/generate-certificates',
+ 'certificate-fix' => 'master-trainer/certificate-fix',
+ 'hvac-documentation' => 'trainer/documentation',
+ 'attendee-profile' => 'trainer/attendee-profile',
+ 'google-sheets' => 'master-trainer/google-sheets',
+ 'communication-templates' => 'trainer/communication-templates',
+ 'communication-schedules' => 'trainer/communication-schedules',
+ 'trainer-registration' => 'trainer/registration',
+ ];
+
+ // Get current page slug
+ global $post;
+ if (!is_page() || !$post) {
+ return;
+ }
+
+ $current_slug = $post->post_name;
+
+ // Check if current page is a legacy URL that needs redirecting
+ if (isset($legacy_redirects[$current_slug])) {
+ // Get current URL path to prevent redirect loops
+ $current_path = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
+ $target_path = $legacy_redirects[$current_slug];
+
+ // Skip redirect if we are already on the target path
+ if ($current_path === $target_path) {
+ return;
+ }
+
+ $new_url = home_url('/' . $legacy_redirects[$current_slug] . '/');
+
+ // Preserve query parameters
+ if (!empty($_SERVER['QUERY_STRING'])) {
+ $new_url .= '?' . $_SERVER['QUERY_STRING'];
+ }
+
+ // Perform 301 redirect
+ wp_redirect($new_url, 301);
+ exit;
+ }
+}
+add_action('template_redirect', 'hvac_ce_handle_legacy_redirects');
+
+/**
+ * Remove custom roles upon plugin deactivation.
+ */
+function hvac_ce_remove_roles() {
+ require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-roles.php';
+ $roles_manager = new HVAC_Roles();
+ $roles_manager->remove_trainer_role();
+ $roles_manager->remove_master_trainer_role();
+ $roles_manager->revoke_admin_dashboard_access();
+
+ // Flush rewrite rules to clean up certificate download URLs
+ flush_rewrite_rules();
+
+ HVAC_Logger::info('Deactivation hook fired, removed hvac_trainer role and admin dashboard access, flushed rewrite rules.', 'Deactivation');
+}
+register_deactivation_hook(__FILE__, 'hvac_ce_remove_roles');
+
+
+
+/**
+ * Enqueue common styles and scripts for HVAC Community Events pages
+ */
+function hvac_ce_enqueue_common_assets() {
+ // Add debug logging to see if function is being called
+
+ // Early return if not on HVAC pages to prevent loading on home page
+ if (is_front_page() || is_home()) {
+ return;
+ }
+
+ // Check if we're on an HVAC plugin page - include both hierarchical and flat page names
+ $hvac_pages = [
+ // Hierarchical page paths
+ 'trainer/dashboard', 'trainer/registration', 'trainer/my-profile',
+ 'trainer/event/manage', 'trainer/event/summary', 'trainer/email-attendees', 'trainer/certificate-reports',
+ 'trainer/generate-certificates', 'master-trainer/certificate-fix', 'trainer/documentation', 'trainer/attendee-profile',
+ 'master-trainer/dashboard', 'master-trainer/google-sheets', 'trainer/communication-templates', 'trainer/communication-schedules',
+ // Flat page names (legacy and backup)
+ 'training-login', 'trainer-dashboard', 'trainer-registration', 'trainer-my-profile',
+ 'trainer-event-manage', 'trainer-event-summary', 'trainer-email-attendees', 'trainer-certificate-reports',
+ 'trainer-generate-certificates', 'master-trainer-certificate-fix', 'trainer-documentation', 'trainer-attendee-profile',
+ 'master-trainer-dashboard', 'master-trainer-google-sheets', 'trainer-communication-templates', 'trainer-communication-schedules',
+ // Child page names only
+ 'dashboard', 'registration', 'my-profile', 'manage', 'summary', 'email-attendees', 'certificate-reports',
+ 'generate-certificates', 'certificate-fix', 'documentation', 'attendee-profile', 'google-sheets', 'communication-templates', 'communication-schedules'
+ ];
+
+ // Check if we're on an HVAC page using multiple methods
+ $is_hvac_page = false;
+
+ // Method 1: Check by page slug/path
+ if (is_page($hvac_pages)) {
+ $is_hvac_page = true;
+ }
+
+ // Method 2: Check by post content containing HVAC shortcodes
+ global $post;
+ if ($post && !$is_hvac_page) {
+ $content = $post->post_content;
+ $hvac_shortcodes = ['hvac_dashboard', 'hvac_master_dashboard', 'hvac_community_login', 'hvac_google_sheets', 'hvac_certificate_reports', 'hvac_generate_certificates'];
+ foreach ($hvac_shortcodes as $shortcode) {
+ if (strpos($content, $shortcode) !== false) {
+ $is_hvac_page = true;
+ break;
+ }
+ }
+ }
+
+ // Method 3: Force enable for testing - check if URL contains known HVAC paths
+ if (!$is_hvac_page && isset($_SERVER['REQUEST_URI'])) {
+ $uri = $_SERVER['REQUEST_URI'];
+ if (strpos($uri, '/trainer/') !== false || strpos($uri, '/master-trainer/') !== false || strpos($uri, '/training-login/') !== false) {
+ $is_hvac_page = true;
+ }
+ }
+
+ // Method 4: For debugging - force enable if admin is logged in and URL suggests HVAC content
+ if (!$is_hvac_page && current_user_can('administrator')) {
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $uri = $_SERVER['REQUEST_URI'];
+ if (strpos($uri, 'hvac') !== false ||
+ strpos($uri, 'certificate') !== false ||
+ strpos($uri, 'dashboard') !== false ||
+ strpos($uri, 'google-sheets') !== false) {
+ $is_hvac_page = true;
+ }
+ }
+ }
+
+ // Method 5: Temporary fix - always load on pages with specific names
+ if ($post && !$is_hvac_page) {
+ $post_name = $post->post_name;
+ $debug_keywords = ['dashboard', 'google-sheets', 'certificate', 'trainer'];
+ foreach ($debug_keywords as $keyword) {
+ if (strpos($post_name, $keyword) !== false) {
+ $is_hvac_page = true;
+ break;
+ }
+ }
+ }
+
+
+ // For now, let's be more permissive to debug the issue
+ if (!$is_hvac_page) {
+ // Temporary: Always load CSS if we're on any non-home page for debugging
+ if (!is_front_page() && !is_home()) {
+ $is_hvac_page = true;
+ }
+ }
+
+ // Only proceed if we're on an HVAC page
+ if (!$is_hvac_page) {
+ return;
+ }
+
+ // Enqueue admin bar hiding script for trainers
+ if (is_user_logged_in()) {
+ $user = wp_get_current_user();
+ $has_trainer_role = in_array('hvac_trainer', $user->roles) || in_array('hvac_master_trainer', $user->roles);
+ $has_admin_role = in_array('administrator', $user->roles);
+
+ if ($has_trainer_role && !$has_admin_role) {
+ wp_enqueue_script(
+ 'hvac-admin-bar-hide',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-admin-bar-hide.js',
+ array(),
+ HVAC_CE_VERSION,
+ false // Load in header for immediate effect
+ );
+ }
+ }
+
+
+ // Enqueue the harmonized framework first - this provides the base styling
+ wp_enqueue_style(
+ 'hvac-harmonized-framework',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-harmonized.css',
+ [], // No dependencies - this is the foundation
+ HVAC_CE_VERSION . '-v3.0.0'
+ );
+
+ // Enqueue the legacy common CSS file for backward compatibility
+ wp_enqueue_style(
+ 'hvac-common-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-common.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION
+ );
+
+ // Enqueue animations CSS file (ONLY on HVAC pages)
+ wp_enqueue_style(
+ 'hvac-animations',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-animations.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION
+ );
+
+ // Enqueue mobile navigation CSS file (ONLY on HVAC pages)
+ wp_enqueue_style(
+ 'hvac-mobile-nav',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-mobile-nav.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION
+ );
+
+ // Enqueue print stylesheet
+ wp_enqueue_style(
+ 'hvac-print-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-print.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION,
+ 'print' // Print media only
+ );
+
+ // Enqueue the accessibility helper JS (ONLY on HVAC pages)
+ wp_enqueue_script(
+ 'hvac-accessibility-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-accessibility.js',
+ [], // No dependencies
+ HVAC_CE_VERSION,
+ true // Load in footer
+ );
+
+ // Enqueue animations JS (ONLY on HVAC pages)
+ wp_enqueue_script(
+ 'hvac-animations-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-animations.js',
+ [], // No dependencies
+ HVAC_CE_VERSION,
+ true // Load in footer
+ );
+
+ // Enqueue mobile navigation JS (ONLY on HVAC pages)
+ wp_enqueue_script(
+ 'hvac-mobile-nav-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-mobile-nav.js',
+ [], // No dependencies
+ HVAC_CE_VERSION,
+ true // Load in footer
+ );
+
+ // Enqueue page-specific enhanced styles based on current page
+ if (is_page('trainer/dashboard')) {
+ wp_enqueue_style(
+ 'hvac-dashboard-enhanced',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-dashboard-enhanced.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION . '-v3.0.0'
+ );
+ // Keep legacy for compatibility
+ wp_enqueue_style(
+ 'hvac-dashboard-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-dashboard.css',
+ ['hvac-dashboard-enhanced'], // Load after enhanced
+ HVAC_CE_VERSION
+ );
+ }
+
+ if (is_page('master-trainer/dashboard')) {
+ // Master dashboard uses same styling as regular dashboard
+ wp_enqueue_style(
+ 'hvac-dashboard-enhanced',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-dashboard-enhanced.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION . '-v3.0.0'
+ );
+ wp_enqueue_style(
+ 'hvac-dashboard-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-dashboard.css',
+ ['hvac-dashboard-enhanced'], // Load after enhanced
+ HVAC_CE_VERSION
+ );
+ }
+
+ if (is_page('training-login')) {
+ wp_enqueue_style(
+ 'hvac-community-login-enhanced',
+ HVAC_CE_PLUGIN_URL . 'assets/css/community-login-enhanced.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION . '-v3.0.0'
+ );
+ // Keep legacy for compatibility
+ wp_enqueue_style(
+ 'hvac-community-login-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/community-login.css',
+ ['hvac-community-login-enhanced'], // Load after enhanced
+ HVAC_CE_VERSION
+ );
+ }
+
+ if (is_page('trainer/registration')) {
+ wp_enqueue_style(
+ 'hvac-registration-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-registration.css',
+ ['hvac-common-style'], // Depends on common styles
+ HVAC_CE_VERSION
+ );
+ }
+
+ if (is_page('trainer/email-attendees')) {
+ wp_enqueue_style(
+ 'hvac-email-attendees-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-email-attendees.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION
+ );
+ }
+
+ if (is_singular(Tribe__Events__Main::POSTTYPE) || is_page('trainer/event/summary')) {
+ wp_enqueue_style(
+ 'hvac-event-summary-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-event-summary.css',
+ ['hvac-common-style'], // Depends on common styles
+ HVAC_CE_VERSION
+ );
+
+ // Enqueue event summary JS for certificate actions
+ wp_enqueue_script(
+ 'hvac-event-summary-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-event-summary.js',
+ ['jquery'], // jQuery dependency
+ HVAC_CE_VERSION,
+ true // Load in footer
+ );
+
+ // Localize script with AJAX data
+ wp_localize_script('hvac-event-summary-js', 'hvacEventSummary', [
+ 'ajaxUrl' => admin_url('admin-ajax.php'),
+ 'certificateNonce' => wp_create_nonce('hvac_certificate_actions')
+ ]);
+ }
+
+ // Enqueue styles for event management page
+ // Force load CSS on event manage page regardless of user status
+ $current_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
+ $is_event_manage_url = strpos($current_url, '/trainer/event/manage') !== false ||
+ strpos($current_url, 'event/manage') !== false ||
+ strpos($current_url, 'event%2Fmanage') !== false;
+
+ // Check by multiple methods to ensure we catch the page
+ $is_event_manage = $is_event_manage_url ||
+ is_page('trainer/event/manage') ||
+ is_page('manage') ||
+ is_page(5344) || // Direct page ID from staging
+ is_page('trainer-event-manage'); // Legacy slug
+
+ // Also check if this is ANY page with Community Events content
+ $has_community_events = false;
+ if (!$is_event_manage && function_exists('tribe_is_community_edit_event_page')) {
+ $has_community_events = tribe_is_community_edit_event_page() || tribe_is_community_my_events_page();
+ }
+
+ // Force CSS for event manage page - this ensures it loads for ALL users
+ if ($is_event_manage || $has_community_events || $is_event_manage_url) {
+ // Enqueue dedicated event management CSS
+ wp_enqueue_style(
+ 'hvac-event-manage-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-event-manage.css',
+ ['hvac-common-style'],
+ HVAC_CE_VERSION
+ );
+
+ // Also enqueue dashboard styles for consistency
+ wp_enqueue_style(
+ 'hvac-dashboard-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-dashboard.css',
+ ['hvac-common-style'],
+ HVAC_CE_VERSION
+ );
+
+ // CSS is now enqueued for event management pages
+ }
+
+ // Enqueue certificate-related styles
+ if (is_page('trainer/certificate-reports') || is_page('trainer/generate-certificates') || is_page('master-trainer/certificate-fix')) {
+ wp_enqueue_style(
+ 'hvac-certificates-enhanced',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-certificates-enhanced.css',
+ ['hvac-harmonized-framework'], // Depends on harmonized framework
+ HVAC_CE_VERSION . '-v3.0.0'
+ );
+ // Keep legacy for compatibility
+ wp_enqueue_style(
+ 'hvac-certificates-admin-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-certificates-admin.css',
+ ['hvac-certificates-enhanced'], // Load after enhanced
+ HVAC_CE_VERSION
+ );
+
+ // Enqueue certificate JS
+ wp_enqueue_script(
+ 'hvac-certificate-admin-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-certificate-admin.js',
+ ['jquery', 'wp-color-picker'], // jQuery dependency
+ HVAC_CE_VERSION,
+ true // Load in footer
+ );
+
+ // Add WordPress color picker if needed
+ wp_enqueue_style('wp-color-picker');
+ wp_enqueue_script('wp-color-picker');
+
+ // Localize script with AJAX data
+ wp_localize_script('hvac-certificate-admin-js', 'hvacCertificateData', [
+ 'ajaxUrl' => admin_url('admin-ajax.php'),
+ 'previewNonce' => wp_create_nonce('hvac_certificate_preview')
+ ]);
+ }
+}
+add_action('wp_enqueue_scripts', 'hvac_ce_enqueue_common_assets');
+
+/**
+ * Enqueue styles and scripts for admin dashboard
+ */
+function hvac_ce_enqueue_admin_assets($hook) {
+ // Only load on our dashboard page
+ if ($hook !== 'hvac-community-events_page_hvac-ce-dashboard') {
+ return;
+ }
+
+ // Enqueue dashboard CSS
+ wp_enqueue_style(
+ 'hvac-admin-dashboard-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/admin-dashboard.css',
+ array('wp-admin'),
+ HVAC_CE_VERSION
+ );
+
+ // Enqueue dashboard JS
+ wp_enqueue_script(
+ 'hvac-admin-dashboard-script',
+ HVAC_CE_PLUGIN_URL . 'assets/js/admin-dashboard.js',
+ array('jquery', 'wp-util'),
+ HVAC_CE_VERSION,
+ true
+ );
+
+ // Localize script with AJAX data
+ wp_localize_script('hvac-admin-dashboard-script', 'hvac_admin_dashboard', array(
+ 'ajax_url' => admin_url('admin-ajax.php'),
+ 'nonce' => wp_create_nonce('hvac_admin_nonce')
+ ));
+}
+
+
+
+// Include the main plugin class
+require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-community-events.php';
+
+// Include the help system
+require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-help-system.php';
+
+// Initialize the plugin
+function hvac_community_events_init() {
+ HVAC_Logger::info('Initializing HVAC Community Events plugin', 'Core');
+ return HVAC_Community_Events::instance();
+}
+add_action('plugins_loaded', 'hvac_community_events_init');
+
+// Redirect /trainer/ to /trainer/dashboard/
+add_action('template_redirect', 'hvac_ce_redirect_trainer_parent_page');
+function hvac_ce_redirect_trainer_parent_page() {
+ // Get the current URL path
+ $current_path = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
+
+ // Check if we're on the trainer parent page (not a child page)
+ if ($current_path === 'trainer' || $current_path === 'trainer/') {
+ // Redirect to the dashboard
+ wp_redirect(home_url('/trainer/dashboard/'), 301);
+ exit;
+ }
+
+ // Also redirect master-trainer to master-trainer/dashboard
+ if ($current_path === 'master-trainer' || $current_path === 'master-trainer/') {
+ wp_redirect(home_url('/master-trainer/dashboard/'), 301);
+ exit;
+ }
+}
+
+
+// Initialize certificate URL handler very early to catch URLs before 404
+function hvac_init_certificate_url_handler() {
+ // Load the certificate URL handler class if not already loaded
+ if (!class_exists('HVAC_Certificate_URL_Handler')) {
+ $handler_file = HVAC_CE_PLUGIN_DIR . 'includes/certificates/class-certificate-url-handler.php';
+ if (file_exists($handler_file)) {
+ require_once $handler_file;
+ }
+ }
+
+ // Initialize the handler if class exists
+ if (class_exists('HVAC_Certificate_URL_Handler')) {
+ HVAC_Certificate_URL_Handler::instance();
+ }
+}
+// Hook very early - before WordPress decides it's a 404
+add_action('muplugins_loaded', 'hvac_init_certificate_url_handler', 1);
+add_action('plugins_loaded', 'hvac_init_certificate_url_handler', 1);
+
+
+/**
+ * Include custom template for single event summary page.
+ *
+ * @param string $template The path of the template to include.
+ * @return string The path of the template file.
+ */
+function hvac_ce_include_event_summary_template( $template ) {
+ // Check if it's a single event post type view
+ if ( is_singular( Tribe__Events__Main::POSTTYPE ) ) {
+ // Check if the custom template exists in the plugin's template directory
+ $custom_template = HVAC_CE_PLUGIN_DIR . 'templates/single-hvac-event-summary.php';
+ if ( file_exists( $custom_template ) ) {
+ // Return the path to the custom template
+ return $custom_template;
+ }
+ }
+ // Return the original template if not a single event or custom template doesn't exist
+ return $template;
+}
+
+/**
+ * Template routing for Order Summary Page.
+ */
+function hvac_ce_include_order_summary_template( $template ) {
+ if ( is_page( 'order-summary' ) && isset( $_GET['order_id'] ) && absint( $_GET['order_id'] ) > 0 ) {
+ $custom_template = HVAC_CE_PLUGIN_DIR . 'templates/single-hvac-order-summary.php';
+ if ( file_exists( $custom_template ) ) {
+ return $custom_template;
+ }
+ }
+ return $template;
+}
+// Removed - template handling is now in the main class
+// add_filter( 'template_include', 'hvac_ce_include_event_summary_template', 99 );
+
+/**
+ * Initialize attendee profile handler
+ */
+function hvac_init_attendee_profile() {
+ // Load the attendee profile class if not already loaded
+ if (!class_exists('HVAC_Attendee_Profile')) {
+ $profile_file = HVAC_CE_PLUGIN_DIR . 'includes/class-attendee-profile.php';
+ if (file_exists($profile_file)) {
+ require_once $profile_file;
+ }
+ }
+
+ // Initialize the handler if class exists
+ if (class_exists('HVAC_Attendee_Profile')) {
+ HVAC_Attendee_Profile::instance();
+ }
+}
+// Initialize on plugins_loaded
+add_action('plugins_loaded', 'hvac_init_attendee_profile', 10);
+
+// Include attendee profile helper functions
+require_once HVAC_CE_PLUGIN_DIR . 'includes/helpers/attendee-profile-link.php';
+
+/**
+ * Handle AJAX request for master dashboard events table
+ */
+function hvac_ajax_master_dashboard_events() {
+ // Verify nonce
+ if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_master_dashboard_nonce')) {
+ wp_die('Security check failed');
+ }
+
+ // Check permissions
+ if (!current_user_can('view_master_dashboard') && !current_user_can('view_all_trainer_data') && !current_user_can('manage_options')) {
+ wp_send_json_error('Insufficient permissions');
+ }
+
+ // Load master dashboard data class if needed
+ if (!class_exists('HVAC_Master_Dashboard_Data')) {
+ require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-master-dashboard-data.php';
+ }
+
+ // Initialize data handler
+ $master_data = new HVAC_Master_Dashboard_Data();
+
+ // Get table data with filters
+ $args = array(
+ 'status' => sanitize_text_field($_POST['status'] ?? 'all'),
+ 'search' => sanitize_text_field($_POST['search'] ?? ''),
+ 'orderby' => sanitize_text_field($_POST['orderby'] ?? 'date'),
+ 'order' => sanitize_text_field($_POST['order'] ?? 'DESC'),
+ 'page' => absint($_POST['page'] ?? 1),
+ 'per_page' => absint($_POST['per_page'] ?? 10),
+ 'date_from' => sanitize_text_field($_POST['date_from'] ?? ''),
+ 'date_to' => sanitize_text_field($_POST['date_to'] ?? ''),
+ 'trainer_id' => absint($_POST['trainer_id'] ?? 0),
+ );
+
+ $table_data = $master_data->get_events_table_data($args);
+ wp_send_json_success($table_data);
+}
+add_action('wp_ajax_hvac_master_dashboard_events', 'hvac_ajax_master_dashboard_events');
+add_action('wp_ajax_nopriv_hvac_master_dashboard_events', 'hvac_ajax_master_dashboard_events');
diff --git a/includes/admin/class-zoho-admin.php b/includes/admin/class-zoho-admin.php
new file mode 100644
index 00000000..76bbb874
--- /dev/null
+++ b/includes/admin/class-zoho-admin.php
@@ -0,0 +1,924 @@
+ admin_url('admin-ajax.php'),
+ 'nonce' => wp_create_nonce('hvac_zoho_nonce')
+ ));
+
+ // Add inline test script for debugging
+ wp_add_inline_script('hvac-zoho-admin', '
+ console.log("Zoho admin script loaded");
+ jQuery(document).ready(function($) {
+ console.log("DOM ready, setting up click handler");
+ $(document).on("click", "#test-zoho-connection", function() {
+ console.log("Test button clicked - inline script");
+ });
+ });
+ ');
+
+ wp_enqueue_style(
+ 'hvac-zoho-admin',
+ HVAC_CE_PLUGIN_URL . 'assets/css/zoho-admin.css',
+ array(),
+ HVAC_CE_VERSION
+ );
+ }
+
+ /**
+ * Render admin page
+ */
+ public function render_admin_page() {
+ $site_url = get_site_url();
+
+ // Debug logging
+
+ // More robust production detection
+ $parsed_url = parse_url($site_url);
+ $host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
+
+ // Remove www prefix for comparison
+ $clean_host = preg_replace('/^www\./', '', $host);
+
+ // Check if this is production
+ $is_production = ($clean_host === 'upskillhvac.com');
+
+ // Double-check with string comparison as fallback
+ if (!$is_production) {
+ $is_production = (strpos($site_url, 'upskillhvac.com') !== false &&
+ strpos($site_url, 'staging') === false &&
+ strpos($site_url, 'test') === false &&
+ strpos($site_url, 'dev') === false &&
+ strpos($site_url, 'cloudwaysapps.com') === false);
+ }
+
+ // Set staging as opposite of production
+ $is_staging = !$is_production;
+
+ error_log('[HVAC Zoho] Is Production: ' . ($is_production ? 'YES' : 'NO'));
+ error_log('[HVAC Zoho] Is Staging: ' . ($is_staging ? 'YES' : 'NO'));
+
+ // Get stored credentials
+ $client_id = get_option('hvac_zoho_client_id', '');
+ $client_secret = get_option('hvac_zoho_client_secret', '');
+ $stored_refresh_token = get_option('hvac_zoho_refresh_token', '');
+ $has_credentials = !empty($client_id) && !empty($client_secret);
+
+ // Handle form submission
+ if (isset($_GET['credentials_saved'])) {
+ echo 'Zoho CRM credentials saved successfully!
';
+ }
+ if (isset($_GET['oauth_success'])) {
+ echo 'OAuth authorization completed successfully!
';
+ }
+ if (isset($_GET['oauth_error'])) {
+ echo 'OAuth authorization failed. Please try again.
';
+ }
+
+ ?>
+
+
Zoho CRM Sync
+
+
+
+
🔧 STAGING MODE ACTIVE
+
Current site:
+
Staging mode is active. Data sync will be simulated only. No actual data will be sent to Zoho CRM.
+
Production sync is only enabled on upskillhvac.com
+
OAuth Redirect URI:
+
Use this redirect URI in your Zoho OAuth app configuration.
+
+
+
+
+
+
🔑 Zoho CRM Credentials
+
+
+
+
+
+
Connection Test
+
Test your Zoho CRM connection to ensure everything is working properly.
+
+
+
+
⚠️ OAuth Authorization Required
+
You have saved credentials but need to authorize the application with Zoho CRM.
+
Click "Authorize with Zoho" above to complete the setup.
+
+
+
Test Connection
+
+
+
+
+
+
Data Sync
+
+
+
Events → Campaigns
+
Sync events from The Events Calendar to Zoho CRM Campaigns
+
Sync Events
+
+
+
+
+
Users → Contacts
+
Sync trainers and attendees to Zoho CRM Contacts
+
Sync Users
+
+
+
+
+
Purchases → Invoices
+
Sync ticket purchases to Zoho CRM Invoices
+
Sync Purchases
+
+
+
+
+
+
Sync Settings
+
+
+
+
+
+
+ 'Simple test works!'));
+ }
+
+ /**
+ * Save Zoho CRM credentials
+ */
+ public function save_credentials() {
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(array('message' => 'Unauthorized access'));
+ return;
+ }
+
+ if (!check_ajax_referer('hvac_zoho_credentials', 'nonce', false)) {
+ wp_send_json_error(array('message' => 'Invalid nonce'));
+ return;
+ }
+
+ $client_id = sanitize_text_field($_POST['zoho_client_id']);
+ $client_secret = sanitize_text_field($_POST['zoho_client_secret']);
+
+ if (empty($client_id) || empty($client_secret)) {
+ wp_send_json_error(array('message' => 'Client ID and Client Secret are required'));
+ return;
+ }
+
+ // Validate Client ID format (should start with "1000.")
+ if (!preg_match('/^1000\.[A-Z0-9]+$/', $client_id)) {
+ wp_send_json_error(array('message' => 'Invalid Client ID format. Should start with "1000."'));
+ return;
+ }
+
+ // Save credentials
+ update_option('hvac_zoho_client_id', $client_id);
+ update_option('hvac_zoho_client_secret', $client_secret);
+
+ // Clear any existing refresh token since credentials changed
+ delete_option('hvac_zoho_refresh_token');
+
+ wp_send_json_success(array(
+ 'message' => 'Credentials saved successfully',
+ 'client_id_preview' => substr($client_id, 0, 10) . '...'
+ ));
+ }
+
+ /**
+ * Flush rewrite rules via AJAX
+ */
+ public function flush_rewrite_rules_ajax() {
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(array('message' => 'Unauthorized access'));
+ return;
+ }
+
+ // Clear any cached rules first
+ wp_cache_delete('rewrite_rules', 'options');
+
+ // Add OAuth rewrite rules multiple ways
+ add_rewrite_rule('^oauth/callback/?$', 'index.php?hvac_oauth_callback=1', 'top');
+ add_rewrite_rule('oauth/callback/?$', 'index.php?hvac_oauth_callback=1', 'top');
+
+ // Force hard flush
+ flush_rewrite_rules(true);
+
+ // Clear cache again
+ wp_cache_delete('rewrite_rules', 'options');
+
+ // Add rules again and soft flush
+ $this->add_oauth_rewrite_rule();
+ flush_rewrite_rules(false);
+
+ // Force WordPress to regenerate rules
+ delete_option('rewrite_rules');
+ wp_cache_delete('rewrite_rules', 'options');
+ $wp_rewrite = $GLOBALS['wp_rewrite'];
+ $wp_rewrite->flush_rules(true);
+
+ // Verify the rule exists after flush
+ $rewrite_rules = get_option('rewrite_rules', array());
+ $oauth_rule_exists = isset($rewrite_rules['^oauth/callback/?$']) || isset($rewrite_rules['oauth/callback/?$']);
+
+ // Log for debugging
+ error_log('[HVAC Zoho] Flush rewrite rules result: ' . ($oauth_rule_exists ? 'SUCCESS' : 'FAILED'));
+ error_log('[HVAC Zoho] Total rules after flush: ' . count($rewrite_rules));
+
+ wp_send_json_success(array(
+ 'message' => 'Rewrite rules flushed successfully',
+ 'oauth_rule_exists' => $oauth_rule_exists,
+ 'total_rules' => count($rewrite_rules),
+ 'rules_sample' => array_slice(array_keys($rewrite_rules), 0, 5)
+ ));
+ }
+
+ /**
+ * Flush rewrite rules on plugin activation
+ */
+ public function flush_rewrite_rules_on_activation() {
+ $this->add_oauth_rewrite_rule();
+ flush_rewrite_rules();
+ }
+
+ /**
+ * Add OAuth query vars
+ */
+ public function add_oauth_query_vars($vars) {
+ // Only add if not already present to avoid duplicates
+ if (!in_array('hvac_oauth_callback', $vars)) {
+ $vars[] = 'hvac_oauth_callback';
+ }
+ return $vars;
+ }
+
+ /**
+ * Add OAuth query vars to public query vars
+ */
+ public function add_public_query_vars() {
+ global $wp;
+ // Check if already added to avoid duplicates
+ if (!in_array('hvac_oauth_callback', $wp->public_query_vars)) {
+ $wp->add_query_var('hvac_oauth_callback');
+ }
+ }
+
+ /**
+ * Parse OAuth request using parse_request hook
+ */
+ public function parse_oauth_request($wp) {
+
+ // Check if this is an OAuth callback request
+ if (preg_match('#^/oauth/callback/?#', $_SERVER['REQUEST_URI'])) {
+
+ // Check if we have the code parameter
+ if (isset($_GET['code'])) {
+ error_log('Processing OAuth callback directly from parse_request');
+ $this->process_oauth_callback();
+ exit;
+ } else {
+
+ wp_die('OAuth callback missing authorization code');
+ }
+ }
+ }
+
+ /**
+ * Add OAuth callback rewrite rule
+ */
+ public function add_oauth_rewrite_rule() {
+ add_rewrite_rule('^oauth/callback/?$', 'index.php?hvac_oauth_callback=1', 'top');
+
+ // Also add alternative rule patterns
+ add_rewrite_rule('oauth/callback/?$', 'index.php?hvac_oauth_callback=1', 'top');
+
+ // Force flush if the rule doesn't exist
+ $rewrite_rules = get_option('rewrite_rules');
+ if (!$rewrite_rules || !isset($rewrite_rules['^oauth/callback/?$'])) {
+ // Set a flag to flush rules on next page load
+ update_option('hvac_oauth_rules_need_flush', true);
+
+ // Also try to flush immediately if we're in admin
+ if (is_admin()) {
+ flush_rewrite_rules(false);
+ }
+ }
+
+ // Check if we need to flush based on flag
+ if (get_option('hvac_oauth_rules_need_flush')) {
+ flush_rewrite_rules(false);
+ delete_option('hvac_oauth_rules_need_flush');
+ }
+ }
+
+ /**
+ * Handle OAuth template redirect
+ */
+ public function handle_oauth_template_redirect() {
+ if (get_query_var('hvac_oauth_callback')) {
+ $this->process_oauth_callback();
+ }
+ }
+
+ /**
+ * Process OAuth callback from Zoho
+ */
+ public function process_oauth_callback() {
+
+ if (!isset($_GET['code'])) {
+
+ wp_die('OAuth callback missing authorization code');
+ }
+
+ error_log('OAuth callback received with code: ' . substr($_GET['code'], 0, 20) . '...');
+
+ // Get credentials from WordPress options
+ $client_id = get_option('hvac_zoho_client_id', '');
+ $client_secret = get_option('hvac_zoho_client_secret', '');
+
+ if (empty($client_id) || empty($client_secret)) {
+ wp_die('OAuth callback error: Missing client credentials. Please configure your Zoho CRM credentials first.');
+ }
+
+ // Exchange authorization code for tokens
+ $token_url = 'https://accounts.zoho.com/oauth/v2/token';
+ $redirect_uri = get_site_url() . '/oauth/callback';
+
+ $token_params = array(
+ 'grant_type' => 'authorization_code',
+ 'client_id' => $client_id,
+ 'client_secret' => $client_secret,
+ 'redirect_uri' => $redirect_uri,
+ 'code' => $_GET['code']
+ );
+
+ error_log('OAuth token exchange params: ' . json_encode(array(
+ 'client_id' => substr($client_id, 0, 20) . '...',
+ 'redirect_uri' => $redirect_uri,
+ 'code' => substr($_GET['code'], 0, 20) . '...'
+ )));
+
+ $response = wp_remote_post($token_url, array(
+ 'body' => $token_params,
+ 'timeout' => 30
+ ));
+
+ if (is_wp_error($response)) {
+ error_log('OAuth token exchange error: ' . $response->get_error_message());
+ wp_redirect(admin_url('admin.php?page=hvac-zoho-sync&oauth_error=1&error_msg=' . urlencode($response->get_error_message())));
+ exit;
+ }
+
+ $body = wp_remote_retrieve_body($response);
+ $token_data = json_decode($body, true);
+
+ // Check for errors in response
+ if (isset($token_data['error'])) {
+ error_log('OAuth error: ' . $token_data['error'] . ' - ' . ($token_data['error_description'] ?? ''));
+ wp_redirect(admin_url('admin.php?page=hvac-zoho-sync&oauth_error=1&error_msg=' . urlencode($token_data['error'])));
+ exit;
+ }
+
+ if (!isset($token_data['access_token'])) {
+
+ wp_redirect(admin_url('admin.php?page=hvac-zoho-sync&oauth_error=1&error_msg=' . urlencode('No access token received')));
+ exit;
+ }
+
+ // Save tokens
+ update_option('hvac_zoho_access_token', $token_data['access_token']);
+ update_option('hvac_zoho_token_expires', time() + ($token_data['expires_in'] ?? 3600));
+
+ // Refresh token might not be returned on subsequent authorizations
+ if (isset($token_data['refresh_token']) && !empty($token_data['refresh_token'])) {
+ update_option('hvac_zoho_refresh_token', $token_data['refresh_token']);
+ error_log('Refresh token saved successfully');
+ } else {
+ error_log('No refresh token in response - checking for existing token');
+ $existing_refresh = get_option('hvac_zoho_refresh_token');
+ if (empty($existing_refresh)) {
+ error_log('WARNING: No refresh token received and no existing token found');
+ // This is critical - we need a refresh token for long-term access
+ // Store a warning but still complete the flow
+ update_option('hvac_zoho_missing_refresh_token', true);
+ } else {
+ error_log('Using existing refresh token');
+ }
+ }
+
+ // Success - redirect to admin page with success message
+ wp_redirect(admin_url('admin.php?page=hvac-zoho-sync&oauth_success=1'));
+ exit;
+ }
+
+ /**
+ * Handle OAuth callback from Zoho (legacy method)
+ */
+ public function handle_oauth_callback() {
+ // This method is kept for backwards compatibility
+ // The main handling is now done in template_redirect
+ return;
+ }
+
+ /**
+ * Test Zoho connection
+ */
+ public function test_connection() {
+ error_log('test_connection method called');
+
+ try {
+ check_ajax_referer('hvac_zoho_nonce', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(array('message' => 'Unauthorized access'));
+ return;
+ }
+
+ // Get credentials from WordPress options
+ $client_id = get_option('hvac_zoho_client_id', '');
+ $client_secret = get_option('hvac_zoho_client_secret', '');
+
+ // Check configuration before attempting connection
+ if (empty($client_id)) {
+ wp_send_json_error(array(
+ 'message' => 'Configuration Error',
+ 'error' => 'Client ID is not configured',
+ 'details' => 'Please enter your Zoho CRM Client ID in the form above',
+ 'help' => 'Get your Client ID from the Zoho Developer Console'
+ ));
+ return;
+ }
+
+ if (empty($client_secret)) {
+ wp_send_json_error(array(
+ 'message' => 'Configuration Error',
+ 'error' => 'Client Secret is not configured',
+ 'details' => 'Please enter your Zoho CRM Client Secret in the form above',
+ 'help' => 'Get your Client Secret from the Zoho Developer Console'
+ ));
+ return;
+ }
+
+ // Check if we have stored refresh token from previous OAuth
+ $stored_refresh_token = get_option('hvac_zoho_refresh_token');
+
+ if (empty($stored_refresh_token)) {
+ error_log('No stored refresh token found, triggering OAuth authorization');
+
+ $site_url = get_site_url();
+ $redirect_uri = $site_url . '/oauth/callback';
+ $scopes = 'ZohoCRM.settings.ALL,ZohoCRM.modules.ALL,ZohoCRM.users.ALL,ZohoCRM.org.ALL,ZohoCRM.bulk.READ';
+ $auth_url = 'https://accounts.zoho.com/oauth/v2/auth?' . http_build_query(array(
+ 'scope' => $scopes,
+ 'client_id' => $client_id,
+ 'response_type' => 'code',
+ 'access_type' => 'offline',
+ 'redirect_uri' => $redirect_uri,
+ 'prompt' => 'consent'
+ ));
+
+ wp_send_json_error(array(
+ 'message' => 'OAuth Authorization Required',
+ 'error' => 'No stored refresh token found',
+ 'details' => 'You have valid credentials but need to complete OAuth authorization to get a fresh token',
+ 'help' => 'Click the "Authorize with Zoho" button above to complete setup',
+ 'next_steps' => array(
+ '1. Click the "Authorize with Zoho" button above',
+ '2. Sign in to your Zoho account',
+ '3. Grant permissions to the application',
+ '4. You will be redirected back and the refresh token will be saved automatically'
+ ),
+ 'auth_url' => $auth_url,
+ 'credentials_status' => array(
+ 'client_id' => substr($client_id, 0, 10) . '...',
+ 'client_secret_exists' => true,
+ 'refresh_token_exists' => false
+ )
+ ));
+ return;
+ }
+
+ // We have a refresh token - test the actual API connection
+ require_once HVAC_CE_PLUGIN_DIR . 'includes/zoho/class-zoho-crm-auth.php';
+ $auth = new HVAC_Zoho_CRM_Auth();
+
+ error_log('Testing API with refresh token: ' . substr($stored_refresh_token, 0, 10) . '...');
+
+ // Test API call
+ $response = $auth->make_api_request('/settings/modules', 'GET');
+
+ if (is_wp_error($response)) {
+ error_log('WordPress HTTP error: ' . $response->get_error_message());
+ wp_send_json_error(array(
+ 'message' => 'API Connection Failed',
+ 'error' => $response->get_error_message(),
+ 'details' => 'WordPress HTTP error occurred'
+ ));
+ return;
+ }
+
+ if (isset($response['error'])) {
+ error_log('Zoho API error: ' . $response['error']);
+
+ // Check if it's an invalid token error
+ if (strpos($response['error'], 'invalid') !== false || strpos($response['error'], 'expired') !== false) {
+
+ // Clear the invalid token and trigger OAuth
+ delete_option('hvac_zoho_refresh_token');
+
+ $auth_url = $auth->get_authorization_url();
+ $site_url = get_site_url();
+ $redirect_uri = $site_url . '/oauth/callback';
+ $scopes = 'ZohoCRM.settings.ALL,ZohoCRM.modules.ALL,ZohoCRM.users.ALL,ZohoCRM.org.ALL,ZohoCRM.bulk.READ';
+ $new_auth_url = 'https://accounts.zoho.com/oauth/v2/auth?' . http_build_query(array(
+ 'scope' => $scopes,
+ 'client_id' => $client_id,
+ 'response_type' => 'code',
+ 'access_type' => 'offline',
+ 'redirect_uri' => $redirect_uri,
+ 'prompt' => 'consent'
+ ));
+
+ wp_send_json_error(array(
+ 'message' => 'OAuth Authorization Required',
+ 'error' => 'Refresh token expired or invalid',
+ 'details' => 'The stored refresh token is no longer valid. Please re-authorize.',
+ 'help' => 'Refresh the page and click "Authorize with Zoho" again',
+ 'next_steps' => array(
+ '1. Refresh this page',
+ '2. Click the "Authorize with Zoho" button',
+ '3. Sign in to your Zoho account',
+ '4. Grant permissions to the application',
+ '5. You will be redirected back and the refresh token will be saved automatically'
+ ),
+ 'auth_url' => $new_auth_url,
+ 'credentials_status' => array(
+ 'client_id' => substr($client_id, 0, 10) . '...',
+ 'client_secret_exists' => true,
+ 'refresh_token_exists' => false
+ )
+ ));
+ return;
+ }
+
+ wp_send_json_error(array(
+ 'message' => 'Zoho API Error',
+ 'error' => $response['error'],
+ 'details' => isset($response['details']) ? $response['details'] : 'No additional details'
+ ));
+ return;
+ }
+
+ // Success!
+ wp_send_json_success(array(
+ 'message' => 'Connection successful!',
+ 'modules' => isset($response['modules']) ? count($response['modules']) . ' modules available' : 'API connected',
+ 'credentials_status' => array(
+ 'client_id' => substr($client_id, 0, 10) . '...',
+ 'client_secret_exists' => true,
+ 'refresh_token_exists' => true,
+ 'api_working' => true
+ )
+ ));
+ } catch (Exception $e) {
+ error_log('Exception in test_connection: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
+ wp_send_json_error(array(
+ 'message' => 'Connection test failed due to exception',
+ 'error' => $e->getMessage(),
+ 'file' => $e->getFile() . ':' . $e->getLine()
+ ));
+ } catch (Error $e) {
+ error_log('PHP Error in test_connection: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
+ wp_send_json_error(array(
+ 'message' => 'Connection test failed due to PHP error',
+ 'error' => $e->getMessage(),
+ 'file' => $e->getFile() . ':' . $e->getLine()
+ ));
+ } catch (Throwable $e) {
+ error_log('Fatal error in test_connection: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
+ wp_send_json_error(array(
+ 'message' => 'Connection test failed due to fatal error',
+ 'error' => $e->getMessage(),
+ 'file' => $e->getFile() . ':' . $e->getLine()
+ ));
+ }
+ }
+
+ /**
+ * Sync data to Zoho
+ */
+ public function sync_data() {
+ check_ajax_referer('hvac_zoho_nonce', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_die('Unauthorized');
+ }
+
+ $type = sanitize_text_field($_POST['type']);
+
+ try {
+ require_once HVAC_CE_PLUGIN_DIR . 'includes/zoho/class-zoho-sync.php';
+ $sync = new HVAC_Zoho_Sync();
+
+ switch ($type) {
+ case 'events':
+ $result = $sync->sync_events();
+ break;
+ case 'users':
+ $result = $sync->sync_users();
+ break;
+ case 'purchases':
+ $result = $sync->sync_purchases();
+ break;
+ default:
+ throw new Exception('Invalid sync type');
+ }
+
+ wp_send_json_success($result);
+ } catch (Exception $e) {
+ wp_send_json_error(array(
+ 'message' => 'Sync failed',
+ 'error' => $e->getMessage()
+ ));
+ }
+ }
+}
+?>
\ No newline at end of file