certificate_manager = HVAC_Certificate_Manager::instance(); $this->certificate_security = HVAC_Certificate_Security::instance(); // Initialize hooks $this->init_hooks(); } /** * Initialize hooks. */ protected function init_hooks() { // Register AJAX handlers add_action('wp_ajax_hvac_get_certificate_url', array($this, 'get_certificate_url')); add_action('wp_ajax_hvac_email_certificate', array($this, 'email_certificate')); add_action('wp_ajax_hvac_revoke_certificate', array($this, 'revoke_certificate')); add_action('wp_ajax_hvac_generate_certificates', array($this, 'generate_certificates')); add_action('wp_ajax_hvac_get_event_attendees', array($this, 'get_event_attendees')); // Enqueue scripts add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts')); } /** * Enqueue scripts and localize data. */ public function enqueue_scripts() { // Only load on certificate pages if (is_page('certificate-reports') || is_page('generate-certificates')) { // Enqueue UX enhancements first wp_enqueue_style( 'hvac-ux-enhancements-css', HVAC_PLUGIN_URL . 'assets/css/hvac-ux-enhancements.css', array(), HVAC_PLUGIN_VERSION ); wp_enqueue_script( 'hvac-ux-enhancements-js', HVAC_PLUGIN_URL . 'assets/js/hvac-ux-enhancements.js', array('jquery'), HVAC_PLUGIN_VERSION, true ); // Enqueue certificate actions JS wp_enqueue_script( 'hvac-certificate-actions-js', HVAC_PLUGIN_URL . 'assets/js/hvac-certificate-actions.js', array('jquery', 'hvac-ux-enhancements-js'), HVAC_PLUGIN_VERSION, true ); // Localize script with AJAX data wp_localize_script('hvac-certificate-actions-js', 'hvacCertificateData', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'viewNonce' => wp_create_nonce('hvac_view_certificate'), 'emailNonce' => wp_create_nonce('hvac_email_certificate'), 'revokeNonce' => wp_create_nonce('hvac_revoke_certificate'), 'generateNonce' => wp_create_nonce('hvac_generate_certificates') )); } } /** * AJAX handler for getting a certificate download URL. */ public function get_certificate_url() { // Verify nonce if ( (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_view_certificate')) && (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_certificate_actions')) ) { wp_send_json_error(array('message' => 'Security check failed')); } // Get certificate by different methods $certificate = null; // Method 1: Direct certificate ID if (isset($_POST['certificate_id']) && absint($_POST['certificate_id'])) { $certificate_id = absint($_POST['certificate_id']); $certificate = $this->certificate_manager->get_certificate($certificate_id); } // Method 2: Event ID and Attendee ID elseif (isset($_POST['event_id']) && isset($_POST['attendee_id'])) { $event_id = absint($_POST['event_id']); $attendee_id = absint($_POST['attendee_id']); $certificate = $this->certificate_manager->get_certificate_by_attendee($event_id, $attendee_id); } else { wp_send_json_error(array('message' => 'Missing certificate information')); } // Check if certificate exists if (!$certificate) { wp_send_json_error(array('message' => 'Certificate not found')); } // Shorthand for certificate ID $certificate_id = $certificate->certificate_id; // Check user permissions (must be the event author or admin) $event = get_post($certificate->event_id); if (!$event || !current_user_can('edit_post', $event->ID)) { wp_send_json_error(array('message' => 'You do not have permission to view this certificate')); } // Get attendee name $attendee_name = get_post_meta($certificate->attendee_id, '_tribe_tickets_full_name', true); if (empty($attendee_name)) { $attendee_name = 'Attendee #' . $certificate->attendee_id; } // Generate secure download URL $certificate_data = array( 'file_path' => $certificate->file_path, 'event_name' => $event->post_title, 'attendee_name' => $attendee_name ); $download_url = $this->certificate_security->generate_download_token($certificate_id, $certificate_data); if (!$download_url) { wp_send_json_error(array('message' => 'Failed to generate download URL')); } wp_send_json_success(array('url' => $download_url)); } /** * AJAX handler for emailing a certificate. */ public function email_certificate() { // Verify nonce if ( (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_email_certificate')) && (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_certificate_actions')) ) { wp_send_json_error(array('message' => 'Security check failed')); } // Get certificate by different methods $certificate = null; // Method 1: Direct certificate ID if (isset($_POST['certificate_id']) && absint($_POST['certificate_id'])) { $certificate_id = absint($_POST['certificate_id']); $certificate = $this->certificate_manager->get_certificate($certificate_id); } // Method 2: Event ID and Attendee ID elseif (isset($_POST['event_id']) && isset($_POST['attendee_id'])) { $event_id = absint($_POST['event_id']); $attendee_id = absint($_POST['attendee_id']); $certificate = $this->certificate_manager->get_certificate_by_attendee($event_id, $attendee_id); } else { wp_send_json_error(array('message' => 'Missing certificate information')); } // Check if certificate exists if (!$certificate) { wp_send_json_error(array('message' => 'Certificate not found')); } // Shorthand for certificate ID $certificate_id = $certificate->certificate_id; // Check if certificate is revoked if ($certificate->revoked) { wp_send_json_error(array('message' => 'Cannot email a revoked certificate')); } // Check user permissions (must be the event author or admin) $event = get_post($certificate->event_id); if (!$event || !current_user_can('edit_post', $event->ID)) { wp_send_json_error(array('message' => 'You do not have permission to email this certificate')); } // Get attendee email $attendee_email = get_post_meta($certificate->attendee_id, '_tribe_tickets_email', true); if (empty($attendee_email)) { wp_send_json_error(array('message' => 'Attendee email not found')); } // Get attendee name $attendee_name = get_post_meta($certificate->attendee_id, '_tribe_tickets_full_name', true); if (empty($attendee_name)) { $attendee_name = 'Attendee'; } // Generate secure download URL (expires in 7 days) $certificate_data = array( 'file_path' => $certificate->file_path, 'event_name' => $event->post_title, 'attendee_name' => $attendee_name ); $download_url = $this->certificate_security->generate_download_token($certificate_id, $certificate_data, 7 * DAY_IN_SECONDS); if (!$download_url) { wp_send_json_error(array('message' => 'Failed to generate download URL')); } // Get current user (sender) info $sender_name = wp_get_current_user()->display_name; // Email subject $subject = sprintf( __('Your Certificate for %s', 'hvac-community-events'), $event->post_title ); // Email body $message = sprintf( __("Hello %s,\n\nThank you for attending %s.\n\nYour certificate of completion is now available. Please click the link below to download your certificate:\n\n%s\n\nThis link will expire in 7 days.\n\nRegards,\n%s", 'hvac-community-events'), $attendee_name, $event->post_title, $download_url, $sender_name ); // Send email $headers = array('Content-Type: text/plain; charset=UTF-8'); $sent = wp_mail($attendee_email, $subject, $message, $headers); if ($sent) { // Record email sent $this->certificate_manager->mark_certificate_emailed($certificate_id); wp_send_json_success(array('message' => 'Certificate sent successfully')); } else { wp_send_json_error(array('message' => 'Failed to send email')); } } /** * AJAX handler for revoking a certificate. */ public function revoke_certificate() { // Verify nonce if ( (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_revoke_certificate')) && (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_certificate_actions')) ) { wp_send_json_error(array('message' => 'Security check failed')); } // Get reason for revocation $reason = isset($_POST['reason']) ? sanitize_text_field($_POST['reason']) : ''; // Get certificate by different methods $certificate = null; // Method 1: Direct certificate ID if (isset($_POST['certificate_id']) && absint($_POST['certificate_id'])) { $certificate_id = absint($_POST['certificate_id']); $certificate = $this->certificate_manager->get_certificate($certificate_id); } // Method 2: Event ID and Attendee ID elseif (isset($_POST['event_id']) && isset($_POST['attendee_id'])) { $event_id = absint($_POST['event_id']); $attendee_id = absint($_POST['attendee_id']); $certificate = $this->certificate_manager->get_certificate_by_attendee($event_id, $attendee_id); } else { wp_send_json_error(array('message' => 'Missing certificate information')); } // Check if certificate exists if (!$certificate) { wp_send_json_error(array('message' => 'Certificate not found')); } // Shorthand for certificate ID $certificate_id = $certificate->certificate_id; // Check if certificate is already revoked if ($certificate->revoked) { wp_send_json_error(array('message' => 'Certificate is already revoked')); } // Check user permissions (must be the event author or admin) $event = get_post($certificate->event_id); if (!$event || !current_user_can('edit_post', $event->ID)) { wp_send_json_error(array('message' => 'You do not have permission to revoke this certificate')); } // Revoke the certificate $revoked = $this->certificate_manager->revoke_certificate( $certificate_id, get_current_user_id(), $reason ); if ($revoked) { // Get updated certificate for revocation date $updated_certificate = $this->certificate_manager->get_certificate($certificate_id); $revoked_date = date_i18n(get_option('date_format'), strtotime($updated_certificate->revoked_date)); wp_send_json_success(array( 'message' => 'Certificate revoked successfully', 'revoked_date' => $revoked_date )); } else { wp_send_json_error(array('message' => 'Failed to revoke certificate')); } } /** * AJAX handler for getting event attendees. */ public function get_event_attendees() { // Verify nonce if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_generate_certificates')) { wp_send_json_error(array('message' => 'Security check failed')); } $event_id = isset($_POST['event_id']) ? absint($_POST['event_id']) : 0; if (!$event_id) { wp_send_json_error(array('message' => 'Event ID is required')); } // Check user permissions $event = get_post($event_id); if (!$event || !current_user_can('edit_post', $event->ID)) { wp_send_json_error(array('message' => 'You do not have permission to view this event')); } // Get attendees using direct database query (same as Generate Certificates template) global $wpdb; $tec_attendees = $wpdb->get_results($wpdb->prepare( "SELECT p.ID as attendee_id, p.post_parent as event_id, COALESCE(tec_full_name.meta_value, tpp_full_name.meta_value, tickets_full_name.meta_value, 'Unknown Attendee') as holder_name, COALESCE(tec_email.meta_value, tpp_email.meta_value, tickets_email.meta_value, tpp_attendee_email.meta_value, 'no-email@example.com') as holder_email, COALESCE(checked_in.meta_value, '0') as check_in FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} tec_full_name ON p.ID = tec_full_name.post_id AND tec_full_name.meta_key = '_tec_tickets_commerce_full_name' LEFT JOIN {$wpdb->postmeta} tpp_full_name ON p.ID = tpp_full_name.post_id AND tpp_full_name.meta_key = '_tribe_tpp_full_name' LEFT JOIN {$wpdb->postmeta} tickets_full_name ON p.ID = tickets_full_name.post_id AND tickets_full_name.meta_key = '_tribe_tickets_full_name' LEFT JOIN {$wpdb->postmeta} tec_email ON p.ID = tec_email.post_id AND tec_email.meta_key = '_tec_tickets_commerce_email' LEFT JOIN {$wpdb->postmeta} tpp_email ON p.ID = tpp_email.post_id AND tpp_email.meta_key = '_tribe_tpp_email' LEFT JOIN {$wpdb->postmeta} tickets_email ON p.ID = tickets_email.post_id AND tickets_email.meta_key = '_tribe_tickets_email' LEFT JOIN {$wpdb->postmeta} tpp_attendee_email ON p.ID = tpp_attendee_email.post_id AND tpp_attendee_email.meta_key = '_tribe_tpp_attendee_email' LEFT JOIN {$wpdb->postmeta} checked_in ON p.ID = checked_in.post_id AND checked_in.meta_key = '_tribe_tickets_attendee_checked_in' WHERE p.post_type IN ('tec_tc_attendee', 'tribe_tpp_attendees') AND p.post_parent = %d ORDER BY p.ID ASC", $event_id )); $attendees = array(); foreach ($tec_attendees as $attendee) { // Check if certificate already exists $has_certificate = $this->certificate_manager->certificate_exists($event_id, $attendee->attendee_id); $attendees[] = array( 'attendee_id' => $attendee->attendee_id, 'event_id' => $attendee->event_id, 'holder_name' => $attendee->holder_name, 'holder_email' => $attendee->holder_email, 'check_in' => intval($attendee->check_in), 'has_certificate' => $has_certificate ); } wp_send_json_success(array( 'attendees' => $attendees, 'event_title' => $event->post_title )); } /** * AJAX handler for generating certificates. */ public function generate_certificates() { // Verify nonce if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_generate_certificates')) { wp_send_json_error(array('message' => 'Security check failed')); } $event_id = isset($_POST['event_id']) ? absint($_POST['event_id']) : 0; $attendee_ids = isset($_POST['attendee_ids']) && is_array($_POST['attendee_ids']) ? array_map('absint', $_POST['attendee_ids']) : array(); $checked_in_only = isset($_POST['checked_in_only']) && $_POST['checked_in_only'] === 'yes'; if (!$event_id) { wp_send_json_error(array('message' => 'Event ID is required')); } if (empty($attendee_ids)) { wp_send_json_error(array('message' => 'Please select at least one attendee')); } // Check user permissions $event = get_post($event_id); if (!$event || !current_user_can('edit_post', $event->ID)) { wp_send_json_error(array('message' => 'You do not have permission to generate certificates for this event')); } // Load certificate generator if (!class_exists('HVAC_Certificate_Generator')) { require_once HVAC_PLUGIN_DIR . 'includes/certificates/class-certificate-generator.php'; } $certificate_generator = HVAC_Certificate_Generator::instance(); // Generate certificates in batch $generation_results = $certificate_generator->generate_certificates_batch( $event_id, $attendee_ids, array(), // Custom data (none for now) get_current_user_id(), // Generated by current user $checked_in_only // Only for checked-in attendees if selected ); // Format response message $message_parts = array(); if ($generation_results['success'] > 0) { $message_parts[] = sprintf('Successfully generated %d certificate(s).', $generation_results['success']); } if ($generation_results['duplicate'] > 0) { $message_parts[] = sprintf('%d duplicate(s) skipped.', $generation_results['duplicate']); } if ($generation_results['not_checked_in'] > 0) { $message_parts[] = sprintf('%d attendee(s) not checked in.', $generation_results['not_checked_in']); } if ($generation_results['error'] > 0) { $message_parts[] = sprintf('%d error(s).', $generation_results['error']); } if ($generation_results['success'] > 0) { // Generate preview URLs for the certificates just created $preview_urls = array(); if (!empty($generation_results['certificate_ids'])) { foreach ($generation_results['certificate_ids'] as $certificate_id) { $certificate = $this->certificate_manager->get_certificate($certificate_id); if ($certificate && $certificate->file_path) { // Generate secure download token for preview $security = HVAC_Certificate_Security::instance(); $preview_url = $security->generate_download_token($certificate_id, array( 'file_path' => $certificate->file_path, 'event_name' => get_the_title($certificate->event_id), 'attendee_name' => $certificate->attendee_name )); if ($preview_url) { $preview_urls[] = array( 'certificate_id' => $certificate_id, 'attendee_name' => $certificate->attendee_name, 'preview_url' => $preview_url ); } } } } wp_send_json_success(array( 'message' => implode(' ', $message_parts), 'results' => $generation_results, 'preview_urls' => $preview_urls )); } else { wp_send_json_error(array( 'message' => 'Failed to generate certificates. ' . implode(' ', $message_parts), 'results' => $generation_results )); } } }