🚨 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>
		
			
				
	
	
		
			322 lines
		
	
	
		
			No EOL
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			322 lines
		
	
	
		
			No EOL
		
	
	
		
			10 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * Certificate Security Class
 | |
|  *
 | |
|  * Handles security aspects of certificate generation and storage.
 | |
|  *
 | |
|  * @package HVAC_Community_Events
 | |
|  * @subpackage Certificates
 | |
|  */
 | |
| 
 | |
| // Exit if accessed directly
 | |
| if (!defined('ABSPATH')) {
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Certificate Security class.
 | |
|  *
 | |
|  * Provides security functions for certificates.
 | |
|  *
 | |
|  * @since 1.0.0
 | |
|  */
 | |
| class HVAC_Certificate_Security {
 | |
| 
 | |
|     /**
 | |
|      * The single instance of the class.
 | |
|      *
 | |
|      * @var HVAC_Certificate_Security
 | |
|      */
 | |
|     protected static $_instance = null;
 | |
| 
 | |
|     /**
 | |
|      * Main HVAC_Certificate_Security Instance.
 | |
|      *
 | |
|      * Ensures only one instance of HVAC_Certificate_Security is loaded or can be loaded.
 | |
|      *
 | |
|      * @return HVAC_Certificate_Security - Main instance.
 | |
|      */
 | |
|     public static function instance() {
 | |
|         if (is_null(self::$_instance)) {
 | |
|             self::$_instance = new self();
 | |
|         }
 | |
|         return self::$_instance;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Constructor.
 | |
|      */
 | |
|     public function __construct() {
 | |
|         // Initialize hooks
 | |
|         add_action('init', array($this, 'init_secure_download'), 1); // Early priority
 | |
|         
 | |
|         // Add admin action to manually flush rewrite rules
 | |
|         add_action('admin_init', array($this, 'maybe_flush_rewrite_rules'));
 | |
|         
 | |
|         // Alternative URL handling without rewrite rules
 | |
|         add_action('parse_request', array($this, 'parse_certificate_request'), 1);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Initialize the secure download endpoint.
 | |
|      */
 | |
|     public function init_secure_download() {
 | |
|         // Add rewrite rule for certificate downloads
 | |
|         add_rewrite_rule(
 | |
|             'hvac-certificate/([^/]+)/?$',
 | |
|             'index.php?certificate_token=$matches[1]',
 | |
|             'top'
 | |
|         );
 | |
|         
 | |
|         // Add query var
 | |
|         add_filter('query_vars', array($this, 'add_query_vars'));
 | |
|         
 | |
|         // Handle certificate download requests
 | |
|         add_action('template_redirect', array($this, 'handle_certificate_download'));
 | |
|     }
 | |
|     
 | |
| 
 | |
|     /**
 | |
|      * Add custom query variables.
 | |
|      *
 | |
|      * @param array $vars Query variables.
 | |
|      * 
 | |
|      * @return array Modified query variables.
 | |
|      */
 | |
|     public function add_query_vars($vars) {
 | |
|         $vars[] = 'certificate_token';
 | |
|         return $vars;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Handle certificate download requests.
 | |
|      */
 | |
|     public function handle_certificate_download() {
 | |
|         $certificate_token = get_query_var('certificate_token');
 | |
|         
 | |
|         if (empty($certificate_token)) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // Validate the token
 | |
|         $certificate_data = $this->validate_download_token($certificate_token);
 | |
|         
 | |
|         if (!$certificate_data) {
 | |
|             wp_die(__('Invalid or expired certificate download link.', 'hvac-community-events'));
 | |
|         }
 | |
|         
 | |
|         // Get file path
 | |
|         $file_path = $this->get_certificate_file_path($certificate_data);
 | |
|         
 | |
|         if (!$file_path || !file_exists($file_path)) {
 | |
|             wp_die(__('Certificate file not found.', 'hvac-community-events'));
 | |
|         }
 | |
|         
 | |
|         // Serve the file
 | |
|         $this->serve_certificate_file($file_path, $certificate_data);
 | |
|         exit;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Parse certificate requests directly without relying on rewrite rules.
 | |
|      * This is a fallback method that works even if rewrite rules fail.
 | |
|      */
 | |
|     public function parse_certificate_request($wp) {
 | |
|         // Only process if we haven't already handled via template_redirect
 | |
|         if (did_action('template_redirect')) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         $request_uri = $_SERVER['REQUEST_URI'];
 | |
|         
 | |
|         // Only match exact certificate download URLs - be very specific
 | |
|         if (preg_match('#^/hvac-certificate/([a-zA-Z0-9]{32})/?$#', $request_uri, $matches)) {
 | |
|             $certificate_token = $matches[1];
 | |
|             
 | |
|             // Validate the token exists (don't delete it yet - let the normal handler do that)
 | |
|             $certificate_data = get_transient('hvac_certificate_token_' . $certificate_token);
 | |
|             
 | |
|             if (!$certificate_data) {
 | |
|                 // Return 404 instead of wp_die to avoid interfering with other pages
 | |
|                 status_header(404);
 | |
|                 return;
 | |
|             }
 | |
|             
 | |
|             // If we have valid certificate data, let the normal template_redirect handler take over
 | |
|             // Set the query var so the normal handler can pick it up
 | |
|             set_query_var('certificate_token', $certificate_token);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Validate a certificate download token.
 | |
|      *
 | |
|      * @param string $token The token to validate.
 | |
|      * 
 | |
|      * @return array|false Certificate data if valid, false otherwise.
 | |
|      */
 | |
|     protected function validate_download_token($token) {
 | |
|         // Check if token exists in transients
 | |
|         $certificate_data = get_transient('hvac_certificate_token_' . $token);
 | |
|         
 | |
|         if (!$certificate_data) {
 | |
|             return false;
 | |
|         }
 | |
|         
 | |
|         // Delete the transient to prevent reuse
 | |
|         delete_transient('hvac_certificate_token_' . $token);
 | |
|         
 | |
|         return $certificate_data;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the full file path for a certificate.
 | |
|      *
 | |
|      * @param array $certificate_data Certificate data.
 | |
|      * 
 | |
|      * @return string|false Full file path or false if not found.
 | |
|      */
 | |
|     protected function get_certificate_file_path($certificate_data) {
 | |
|         if (empty($certificate_data['file_path'])) {
 | |
|             return false;
 | |
|         }
 | |
|         
 | |
|         $upload_dir = wp_upload_dir();
 | |
|         $file_path = $upload_dir['basedir'] . '/' . $certificate_data['file_path'];
 | |
|         
 | |
|         if (file_exists($file_path)) {
 | |
|             return $file_path;
 | |
|         }
 | |
|         
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Serve a certificate file for download.
 | |
|      *
 | |
|      * @param string $file_path       Full path to certificate file.
 | |
|      * @param array  $certificate_data Certificate data.
 | |
|      */
 | |
|     protected function serve_certificate_file($file_path, $certificate_data) {
 | |
|         // Get file information
 | |
|         $file_name = basename($file_path);
 | |
|         $file_size = filesize($file_path);
 | |
|         $file_ext = pathinfo($file_path, PATHINFO_EXTENSION);
 | |
|         
 | |
|         // Set download filename
 | |
|         $event_name = sanitize_title($certificate_data['event_name'] ?? 'event');
 | |
|         $attendee_name = sanitize_title($certificate_data['attendee_name'] ?? 'attendee');
 | |
|         $download_filename = "certificate-{$event_name}-{$attendee_name}.{$file_ext}";
 | |
|         
 | |
|         // Send headers
 | |
|         nocache_headers();
 | |
|         header('Content-Type: application/pdf');
 | |
|         header('Content-Disposition: attachment; filename="' . $download_filename . '"');
 | |
|         header('Content-Transfer-Encoding: binary');
 | |
|         header('Content-Length: ' . $file_size);
 | |
|         
 | |
|         // Disable output buffering
 | |
|         if (ob_get_level()) {
 | |
|             ob_end_clean();
 | |
|         }
 | |
|         
 | |
|         // Output the file
 | |
|         readfile($file_path);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Generate a secure download token for a certificate.
 | |
|      *
 | |
|      * @param int   $certificate_id The certificate ID.
 | |
|      * @param array $certificate_data Additional certificate data.
 | |
|      * @param int   $expiry          Token expiry time in seconds (default 1 hour).
 | |
|      * 
 | |
|      * @return string|false The download URL or false on failure.
 | |
|      */
 | |
|     public function generate_download_token($certificate_id, $certificate_data, $expiry = 3600) {
 | |
|         if (!$certificate_id || empty($certificate_data['file_path'])) {
 | |
|             return false;
 | |
|         }
 | |
|         
 | |
|         // Generate a unique token
 | |
|         $token = wp_generate_password(32, false);
 | |
|         
 | |
|         // Store in transient
 | |
|         set_transient('hvac_certificate_token_' . $token, $certificate_data, $expiry);
 | |
|         
 | |
|         // Generate URL
 | |
|         return home_url('hvac-certificate/' . $token);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a secure storage directory for certificates.
 | |
|      *
 | |
|      * @param string $dir_path The directory path to secure.
 | |
|      * 
 | |
|      * @return bool True if successful, false otherwise.
 | |
|      */
 | |
|     public function create_secure_directory($dir_path) {
 | |
|         // Check if directory exists
 | |
|         if (!file_exists($dir_path)) {
 | |
|             // Create directory
 | |
|             if (!wp_mkdir_p($dir_path)) {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Create/update .htaccess file
 | |
|         $htaccess_content = "# Prevent direct access to files\n";
 | |
|         $htaccess_content .= "<Files ~ \".*\">\n";
 | |
|         $htaccess_content .= "    Order Allow,Deny\n";
 | |
|         $htaccess_content .= "    Deny from all\n";
 | |
|         $htaccess_content .= "</Files>\n";
 | |
|         $htaccess_content .= "# Prevent directory listing\n";
 | |
|         $htaccess_content .= "Options -Indexes\n";
 | |
|         
 | |
|         $htaccess_file = $dir_path . '/.htaccess';
 | |
|         
 | |
|         if (!@file_put_contents($htaccess_file, $htaccess_content)) {
 | |
|             return false;
 | |
|         }
 | |
|         
 | |
|         // Create empty index.php
 | |
|         $index_content = "<?php\n// Silence is golden.";
 | |
|         $index_file = $dir_path . '/index.php';
 | |
|         
 | |
|         if (!@file_put_contents($index_file, $index_content)) {
 | |
|             return false;
 | |
|         }
 | |
|         
 | |
|         return true;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Check if we need to flush rewrite rules.
 | |
|      * This provides a way to manually trigger a flush via URL parameter.
 | |
|      */
 | |
|     public function maybe_flush_rewrite_rules() {
 | |
|         // Only allow admins to flush rewrite rules
 | |
|         if (!current_user_can('manage_options')) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // Check for flush parameter
 | |
|         if (isset($_GET['hvac_flush_rewrite_rules']) && $_GET['hvac_flush_rewrite_rules'] === '1') {
 | |
|             // Re-register our rewrite rule
 | |
|             $this->init_secure_download();
 | |
|             
 | |
|             // Flush the rules
 | |
|             flush_rewrite_rules();
 | |
|             
 | |
|             // Log the action
 | |
|             if (class_exists('HVAC_Logger')) {
 | |
|                 HVAC_Logger::info('Rewrite rules flushed manually via admin parameter', 'Certificate Security');
 | |
|             }
 | |
|             
 | |
|             // Redirect to remove the parameter
 | |
|             wp_redirect(remove_query_arg('hvac_flush_rewrite_rules'));
 | |
|             exit;
 | |
|         }
 | |
|     }
 | |
| } |