certificate_manager = HVAC_Certificate_Manager::instance(); } /** * Generate a certificate for an attendee. * * @param int $event_id The event ID. * @param int $attendee_id The attendee ID. * @param array $custom_data Optional custom data to override defaults. * @param int $generated_by The ID of the user who generated the certificate. * * @return int|false The certificate ID if successful, false otherwise. */ public function generate_certificate($event_id, $attendee_id, $custom_data = array(), $generated_by = 0) { // Check if certificate already exists if ($this->certificate_manager->certificate_exists($event_id, $attendee_id)) { HVAC_Logger::warning("Certificate already exists for event $event_id and attendee $attendee_id", 'Certificates'); return false; } // Get attendee data $attendee_data = $this->get_attendee_data($attendee_id); if (empty($attendee_data)) { HVAC_Logger::error("Failed to retrieve attendee data for ID: $attendee_id", 'Certificates'); return false; } // Get event data $event_data = $this->get_event_data($event_id); if (empty($event_data)) { HVAC_Logger::error("Failed to retrieve event data for ID: $event_id", 'Certificates'); return false; } // Merge custom data $certificate_data = array_merge($attendee_data, $event_data, $custom_data); // Create certificate record first $user_id = $attendee_data['user_id'] ?? 0; $certificate_id = $this->certificate_manager->create_certificate($event_id, $attendee_id, $user_id, '', $generated_by); if (!$certificate_id) { HVAC_Logger::error("Failed to create certificate record for event $event_id and attendee $attendee_id", 'Certificates'); return false; } // Generate PDF and get file path $file_path = $this->generate_pdf($certificate_id, $certificate_data); if (!$file_path) { // Delete the certificate record if PDF generation failed $this->certificate_manager->delete_certificate($certificate_id); HVAC_Logger::error("Failed to generate PDF for certificate ID: $certificate_id", 'Certificates'); return false; } // Generate PNG version for preview purposes $png_path = $this->generate_png($certificate_id, $certificate_data); if ($png_path) { HVAC_Logger::info("Generated PNG version: $png_path", 'Certificates'); } // Update certificate record with file paths $this->certificate_manager->update_certificate_file($certificate_id, $file_path, $png_path); return $certificate_id; } /** * Generate a PDF certificate. * * @param int $certificate_id The certificate ID. * @param array $certificate_data The certificate data. * * @return string|false The relative file path if successful, false otherwise. */ protected function generate_pdf($certificate_id, $certificate_data) { // Get certificate and verify it exists $certificate = $this->certificate_manager->get_certificate($certificate_id); if (!$certificate) { return false; } // Create a custom TCPDF class extension (for header/footer) $pdf = $this->create_certificate_pdf(); // Add a page $pdf->AddPage('L', 'LETTER'); // Landscape, Letter size // Set document metadata $event_name = $certificate_data['event_name'] ?? 'HVAC Training'; $attendee_name = $certificate_data['attendee_name'] ?? 'Attendee'; $pdf->SetCreator('HVAC Community Events'); $pdf->SetAuthor('Upskill HVAC'); $pdf->SetTitle("Certificate of Completion - $event_name"); $pdf->SetSubject("Certificate for $attendee_name"); $pdf->SetKeywords("HVAC, Certificate, Training, $event_name"); // Render certificate content $this->render_certificate_content($pdf, $certificate, $certificate_data); // Get certificate storage path $upload_dir = wp_upload_dir(); $cert_dir = $upload_dir['basedir'] . '/' . get_option('hvac_certificate_storage_path', 'hvac-certificates'); // Create directory if it doesn't exist if (!file_exists($cert_dir)) { wp_mkdir_p($cert_dir); } // Define file name and path $file_name = sanitize_file_name( 'certificate-' . $certificate->certificate_number . '-' . sanitize_title($attendee_name) . '.pdf' ); $event_dir = $cert_dir . '/' . $certificate->event_id; // Create event directory if it doesn't exist if (!file_exists($event_dir)) { wp_mkdir_p($event_dir); } $full_path = $event_dir . '/' . $file_name; $relative_path = get_option('hvac_certificate_storage_path', 'hvac-certificates') . '/' . $certificate->event_id . '/' . $file_name; // Save the PDF file try { $pdf->Output($full_path, 'F'); // F means save to file if (file_exists($full_path)) { return $relative_path; } } catch (Exception $e) { HVAC_Logger::error("Failed to save PDF file: " . $e->getMessage(), 'Certificates'); } return false; } /** * Generate a PNG version of the certificate for preview purposes. * * @param int $certificate_id The certificate ID. * @param array $certificate_data The certificate data. * * @return string|false The relative file path if successful, false otherwise. */ protected function generate_png($certificate_id, $certificate_data) { // Get certificate and verify it exists $certificate = $this->certificate_manager->get_certificate($certificate_id); if (!$certificate) { return false; } // Create PDF for conversion to PNG $pdf = $this->create_certificate_pdf(); $pdf->AddPage(); // Render certificate content $this->render_certificate_content($pdf, $certificate, $certificate_data); // Get certificate storage path $upload_dir = wp_upload_dir(); $cert_dir = $upload_dir['basedir'] . '/' . get_option('hvac_certificate_storage_path', 'hvac-certificates'); // Create directory if it doesn't exist if (!file_exists($cert_dir)) { wp_mkdir_p($cert_dir); } $attendee_name = isset($certificate_data['attendee_name']) ? $certificate_data['attendee_name'] : 'unknown'; // Define file name and path $file_name = sanitize_file_name( 'certificate-' . $certificate->certificate_number . '-' . sanitize_title($attendee_name) . '.png' ); $event_dir = $cert_dir . '/' . $certificate->event_id; // Create event directory if it doesn't exist if (!file_exists($event_dir)) { wp_mkdir_p($event_dir); } $full_path = $event_dir . '/' . $file_name; $relative_path = get_option('hvac_certificate_storage_path', 'hvac-certificates') . '/' . $certificate->event_id . '/' . $file_name; // Convert PDF to PNG using TCPDF's image output try { // Set high DPI for better quality $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); // Output as PNG (using TCPDF's built-in PNG output) // Note: This requires TCPDF to be compiled with PNG support $pdf->Output($full_path, 'F'); // Save PDF first // Convert PDF to PNG using ImageMagick if available if (class_exists('Imagick')) { $imagick = new Imagick(); $imagick->setResolution(300, 300); // High resolution $imagick->readImage($full_path); // Read the PDF $imagick->setImageFormat('png'); $imagick->setImageCompressionQuality(90); // Replace .pdf with .png in the path $png_full_path = str_replace('.pdf', '.png', $full_path); $png_relative_path = str_replace('.pdf', '.png', $relative_path); $imagick->writeImage($png_full_path); $imagick->clear(); if (file_exists($png_full_path)) { return $png_relative_path; } } else { // Fallback: Log that ImageMagick is not available HVAC_Logger::info("ImageMagick not available for PNG conversion. PNG generation skipped.", 'Certificates'); return false; } } catch (Exception $e) { HVAC_Logger::error("Failed to generate PNG file: " . $e->getMessage(), 'Certificates'); } return false; } /** * Create a TCPDF instance for certificate generation. * * @return TCPDF The TCPDF instance. */ protected function create_certificate_pdf() { // Create new PDF document $pdf = new TCPDF('L', 'mm', 'LETTER', true, 'UTF-8', false); // Set document information $pdf->SetTitle('Certificate of Completion'); $pdf->SetAuthor('Upskill HVAC'); // Set margins $pdf->SetMargins(15, 15, 15); // Remove default header/footer $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); // Set auto page breaks $pdf->SetAutoPageBreak(false, 0); // Set default font $pdf->SetFont('helvetica', '', 10); return $pdf; } /** * Render certificate content on PDF. * * @param TCPDF $pdf The TCPDF instance. * @param object $certificate The certificate object. * @param array $certificate_data The certificate data. */ protected function render_certificate_content($pdf, $certificate, $certificate_data) { // Set background image if available $this->add_certificate_background($pdf); // Add Upskill HVAC logo at the top $this->add_upskill_logo($pdf); // Certificate title $pdf->SetFont('helvetica', 'B', 30); $pdf->SetTextColor(0, 77, 155); // Blue $pdf->SetY(45); // Moved down to accommodate logo $pdf->Cell(0, 20, 'CERTIFICATE OF COMPLETION', 0, 1, 'C'); // Description text $pdf->SetFont('helvetica', '', 12); $pdf->SetTextColor(77, 77, 77); // Dark gray $pdf->SetY(70); $pdf->Cell(0, 10, 'This certificate is awarded to', 0, 1, 'C'); // Attendee name - prominently displayed $attendee_name = $certificate_data['attendee_name'] ?? 'Attendee Name'; $pdf->SetFont('helvetica', 'B', 26); $pdf->SetTextColor(0, 0, 0); // Black $pdf->Cell(0, 20, $attendee_name, 0, 1, 'C'); // Course completion text $pdf->SetFont('helvetica', '', 12); $pdf->SetTextColor(77, 77, 77); // Dark gray $pdf->Cell(0, 10, 'for successfully completing', 0, 1, 'C'); // Event name $event_name = $certificate_data['event_name'] ?? 'HVAC Training Course'; $pdf->SetFont('helvetica', 'B', 18); $pdf->SetTextColor(0, 77, 155); // Blue $pdf->Cell(0, 15, $event_name, 0, 1, 'C'); // Event date $event_date = $certificate_data['event_date_formatted'] ?? date('F j, Y'); $pdf->SetFont('helvetica', '', 12); $pdf->SetTextColor(77, 77, 77); // Dark gray $pdf->Cell(0, 10, 'on ' . $event_date, 0, 1, 'C'); // Get instructor/trainer name properly $instructor_name = $certificate_data['instructor_name'] ?? ''; if (empty($instructor_name) && !empty($certificate_data['trainer_name'])) { $instructor_name = $certificate_data['trainer_name']; } if (empty($instructor_name)) { // Try to get from event organizer $instructor_name = $certificate_data['organization_name'] ?? 'Instructor'; } // Draw a line for signature $pdf->SetDrawColor(0, 77, 155); // Blue $pdf->SetLineWidth(0.5); $pdf->Line(70, 155, 190, 155); // Add instructor signature if available if (!empty($certificate_data['instructor_signature'])) { $signature_path = $certificate_data['instructor_signature']; if (file_exists($signature_path)) { $pdf->Image($signature_path, 110, 135, 40, 0, '', '', '', false, 300); } } // Instructor name and title $pdf->SetY(160); $pdf->SetFont('helvetica', 'B', 14); $pdf->SetTextColor(0, 0, 0); // Black $pdf->Cell(0, 10, $instructor_name, 0, 1, 'C'); $pdf->SetFont('helvetica', '', 11); $pdf->SetTextColor(77, 77, 77); // Dark gray $pdf->Cell(0, 8, 'Instructor / Trainer', 0, 1, 'C'); // Add organization name $organization_name = 'Upskill HVAC'; $pdf->SetY(180); $pdf->SetFont('helvetica', 'B', 11); $pdf->SetTextColor(0, 0, 0); // Black $pdf->Cell(0, 8, $organization_name, 0, 1, 'C'); // Add venue info if available $venue_name = $certificate_data['venue_name'] ?? ''; if (!empty($venue_name)) { $pdf->SetFont('helvetica', '', 10); $pdf->SetTextColor(77, 77, 77); // Dark gray $pdf->Cell(0, 8, $venue_name, 0, 1, 'C'); } // Add certificate details at the bottom $pdf->SetFont('helvetica', '', 8); $pdf->SetTextColor(128, 128, 128); // Light gray $pdf->SetY(195); $pdf->Cell(0, 10, 'Certificate #: ' . $certificate->certificate_number . ' | Issue Date: ' . date('F j, Y', strtotime($certificate->date_generated)), 0, 1, 'C'); // Add decorative elements (optional) $this->add_decorative_elements($pdf); } /** * Add certificate background. * * @param TCPDF $pdf The TCPDF instance. */ protected function add_certificate_background($pdf) { // Check if custom background exists $background_path = HVAC_PLUGIN_DIR . 'assets/images/certificate-background.jpg'; if (file_exists($background_path)) { // Add background $pdf->Image($background_path, 0, 0, $pdf->getPageWidth(), $pdf->getPageHeight(), '', '', '', false, 300); } else { // Create a simple background with border $pdf->SetFillColor(255, 255, 255); $pdf->Rect(0, 0, $pdf->getPageWidth(), $pdf->getPageHeight(), 'F'); // Add border $pdf->SetDrawColor(0, 77, 155); // Blue $pdf->SetLineWidth(1.5); $pdf->Rect(5, 5, $pdf->getPageWidth() - 10, $pdf->getPageHeight() - 10, 'D'); // Add inner border $pdf->SetDrawColor(200, 200, 200); // Light gray $pdf->SetLineWidth(0.5); $pdf->Rect(10, 10, $pdf->getPageWidth() - 20, $pdf->getPageHeight() - 20, 'D'); } } /** * Add logo to certificate. * * @param TCPDF $pdf The TCPDF instance. */ protected function add_logo($pdf) { // Check if logo exists $logo_path = HVAC_PLUGIN_DIR . 'assets/images/certificate-logo.png'; if (file_exists($logo_path)) { // Add logo at top left $pdf->Image($logo_path, 15, 15, 40, 0, '', '', '', false, 300); } } /** * Add Upskill HVAC logo to certificate. * * @param TCPDF $pdf The TCPDF instance. */ protected function add_upskill_logo($pdf) { // Check for uploaded logo in WordPress uploads directory $upload_dir = wp_upload_dir(); $logo_path = $upload_dir['basedir'] . '/2025/05/UpskillHVAC-Logo_Black_NoOutline.png'; // Fallback to 2024 directory if 2025 doesn't exist if (!file_exists($logo_path)) { $logo_path = $upload_dir['basedir'] . '/2024/05/UpskillHVAC-Logo_Black_NoOutline.png'; } // Check plugin assets directory as another fallback if (!file_exists($logo_path)) { $logo_path = HVAC_PLUGIN_DIR . 'assets/images/upskill-hvac-logo.png'; } if (file_exists($logo_path)) { // Add logo at top center // Calculate center position - assuming letter size landscape (279.4mm wide) $page_width = $pdf->getPageWidth(); $logo_width = 50; // 50mm wide logo $x_position = ($page_width - $logo_width) / 2; $pdf->Image($logo_path, $x_position, 10, $logo_width, 0, '', '', '', false, 300); } else { // If no logo found, add text branding $pdf->SetFont('helvetica', 'B', 16); $pdf->SetTextColor(0, 77, 155); // Blue $pdf->SetY(15); $pdf->Cell(0, 10, 'UPSKILL HVAC', 0, 1, 'C'); } } /** * Add decorative elements to certificate. * * @param TCPDF $pdf The TCPDF instance. */ protected function add_decorative_elements($pdf) { // Add decorative corner elements $pdf->SetDrawColor(0, 77, 155); // Blue $pdf->SetLineWidth(0.5); // Top left corner $pdf->Line(10, 10, 30, 10); $pdf->Line(10, 10, 10, 30); // Top right corner $page_width = $pdf->getPageWidth(); $pdf->Line($page_width - 30, 10, $page_width - 10, 10); $pdf->Line($page_width - 10, 10, $page_width - 10, 30); // Bottom left corner $page_height = $pdf->getPageHeight(); $pdf->Line(10, $page_height - 30, 10, $page_height - 10); $pdf->Line(10, $page_height - 10, 30, $page_height - 10); // Bottom right corner $pdf->Line($page_width - 30, $page_height - 10, $page_width - 10, $page_height - 10); $pdf->Line($page_width - 10, $page_height - 30, $page_width - 10, $page_height - 10); } /** * Get attendee data from Event Tickets. * * @param int $attendee_id The attendee ID. * * @return array Attendee data. */ protected function get_attendee_data($attendee_id) { $attendee_data = array(); // Get attendee post $attendee = get_post($attendee_id); if (!$attendee) { return $attendee_data; } // Try multiple meta keys for attendee name (matching the template query) $meta_keys_for_name = array( '_tec_tickets_commerce_full_name', // TEC Commerce '_tribe_tpp_full_name', // Tribe PayPal Tickets '_tribe_tickets_full_name', // Event Tickets '_tribe_rsvp_full_name', // RSVP 'attendee_full_name', // Legacy '_name', // Generic 'name' // Generic ); $attendee_name = ''; foreach ($meta_keys_for_name as $meta_key) { $name = get_post_meta($attendee_id, $meta_key, true); if (!empty($name)) { $attendee_name = $name; break; } } // If still no name, try first and last name separately if (empty($attendee_name)) { $first_name_keys = array( '_tribe_tickets_first_name', '_tribe_tpp_first_name', '_tec_tickets_commerce_first_name', 'attendee_first_name', 'first_name' ); $last_name_keys = array( '_tribe_tickets_last_name', '_tribe_tpp_last_name', '_tec_tickets_commerce_last_name', 'attendee_last_name', 'last_name' ); $first_name = ''; $last_name = ''; foreach ($first_name_keys as $key) { $fname = get_post_meta($attendee_id, $key, true); if (!empty($fname)) { $first_name = $fname; break; } } foreach ($last_name_keys as $key) { $lname = get_post_meta($attendee_id, $key, true); if (!empty($lname)) { $last_name = $lname; break; } } if (!empty($first_name) || !empty($last_name)) { $attendee_name = trim($first_name . ' ' . $last_name); } } // Try multiple meta keys for email (matching the template query) $meta_keys_for_email = array( '_tec_tickets_commerce_email', '_tribe_tpp_email', '_tribe_tickets_email', '_tribe_tpp_attendee_email', '_tribe_rsvp_email', 'attendee_email', '_email', 'email' ); $attendee_email = ''; foreach ($meta_keys_for_email as $meta_key) { $email = get_post_meta($attendee_id, $meta_key, true); if (!empty($email) && is_email($email)) { $attendee_email = $email; break; } } // Try to find user by email $user_id = 0; if (!empty($attendee_email)) { $user = get_user_by('email', $attendee_email); if ($user) { $user_id = $user->ID; } } // If still no name, use email prefix or "Attendee" if (empty($attendee_name)) { if (!empty($attendee_email)) { $email_parts = explode('@', $attendee_email); $attendee_name = ucwords(str_replace(array('.', '_', '-'), ' ', $email_parts[0])); } else { $attendee_name = 'Attendee #' . $attendee_id; } } // Build attendee data $attendee_data = array( 'attendee_id' => $attendee_id, 'attendee_name' => $attendee_name, 'attendee_email' => $attendee_email, 'user_id' => $user_id ); // Log for debugging if (defined('WP_DEBUG') && WP_DEBUG && empty($attendee_name)) { error_log("Certificate Generator: No name found for attendee ID $attendee_id"); } return $attendee_data; } /** * Get event data from The Events Calendar. * * @param int $event_id The event ID. * * @return array Event data. */ protected function get_event_data($event_id) { $event_data = array(); // Get event post $event = get_post($event_id); if (!$event) { return $event_data; } // Get event details $event_name = $event->post_title; $event_date = tribe_get_start_date($event_id, false, 'F j, Y'); // Get venue details $venue_id = tribe_get_venue_id($event_id); $venue_name = tribe_get_venue($event_id); // Get organizer details $organizer_id = tribe_get_organizer_id($event_id); $organizer_name = tribe_get_organizer($event_id); // Get trainer/instructor name // First check if this event has a trainer/author $trainer_id = $event->post_author; $trainer = get_userdata($trainer_id); $instructor_name = ''; if ($trainer) { // Try to get display name first $instructor_name = $trainer->display_name; // If no display name, try full name if (empty($instructor_name) || $instructor_name == $trainer->user_login) { $first_name = get_user_meta($trainer_id, 'first_name', true); $last_name = get_user_meta($trainer_id, 'last_name', true); if ($first_name || $last_name) { $instructor_name = trim($first_name . ' ' . $last_name); } } // Fallback to organizer name if still empty if (empty($instructor_name)) { $instructor_name = $organizer_name; } } else { // Use organizer name as fallback $instructor_name = $organizer_name; } // Build event data $event_data = array( 'event_id' => $event_id, 'event_name' => $event_name, 'event_date' => get_post_meta($event_id, '_EventStartDate', true), 'event_date_formatted' => $event_date, 'venue_id' => $venue_id, 'venue_name' => $venue_name, 'organizer_id' => $organizer_id, 'organization_name' => $organizer_name, 'instructor_name' => $instructor_name, 'trainer_name' => $instructor_name, // Also include as trainer_name for compatibility 'trainer_id' => $trainer_id ); return $event_data; } /** * Generate certificates in batch. * * @param int $event_id The event ID. * @param array $attendee_ids Array of attendee IDs. * @param array $custom_data Optional custom data to override defaults. * @param int $generated_by The ID of the user who generated the certificates. * @param bool $checked_in_only Whether to generate certificates only for checked-in attendees. * * @return array Results with success and error counts. */ public function generate_certificates_batch($event_id, $attendee_ids, $custom_data = array(), $generated_by = 0, $checked_in_only = false) { $results = array( 'success' => 0, 'error' => 0, 'duplicate' => 0, 'not_checked_in' => 0, 'certificate_ids' => array() ); if (empty($attendee_ids) || !is_array($attendee_ids)) { return $results; } foreach ($attendee_ids as $attendee_id) { // Check if certificate already exists if ($this->certificate_manager->certificate_exists($event_id, $attendee_id)) { $results['duplicate']++; continue; } // Check attendee check-in status if required if ($checked_in_only) { $is_checked_in = $this->is_attendee_checked_in($attendee_id); if (!$is_checked_in) { $results['not_checked_in']++; continue; } } $certificate_id = $this->generate_certificate($event_id, $attendee_id, $custom_data, $generated_by); if ($certificate_id) { $results['success']++; $results['certificate_ids'][] = $certificate_id; } else { $results['error']++; } } return $results; } /** * Check if an attendee is checked in. * * @param int $attendee_id The attendee ID. * * @return bool True if checked in, false otherwise. */ protected function is_attendee_checked_in($attendee_id) { // Get attendee check-in status from Event Tickets $check_in = get_post_meta($attendee_id, '_tribe_rsvp_checkedin', true); // For Event Tickets Plus we need to check a different meta key if (empty($check_in)) { $check_in = get_post_meta($attendee_id, '_tribe_tpp_checkedin', true); } // If still empty, check the more general meta key if (empty($check_in)) { $check_in = get_post_meta($attendee_id, '_tribe_checkedin', true); } return !empty($check_in) && $check_in == 1; } }