feat: Add direct certificate URL handler to fix 404 errors

- Created new HVAC_Certificate_URL_Handler class that intercepts certificate URLs directly
- Hooks into multiple early WordPress actions (plugins_loaded, init, parse_request)
- Bypasses WordPress rewrite rules for more reliable certificate URL handling
- Added early initialization in main plugin file before WordPress routing
- Includes comprehensive test tool for certificate URL verification

This should resolve certificate viewing 404 errors by handling URLs before WordPress routing.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
bengizmo 2025-05-24 09:07:50 -03:00
parent 29c1d88082
commit b9ecb37260
4 changed files with 405 additions and 0 deletions

View file

@ -466,6 +466,25 @@ function hvac_community_events_init() {
} }
add_action('plugins_loaded', 'hvac_community_events_init'); add_action('plugins_loaded', 'hvac_community_events_init');
// 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. * Include custom template for single event summary page.

View file

@ -0,0 +1,213 @@
<?php
/**
* Certificate URL Handler Class
*
* Handles certificate URL routing without relying on rewrite rules
*
* @package HVAC_Community_Events
* @subpackage Certificates
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
/**
* Certificate URL Handler class.
*
* @since 1.0.0
*/
class HVAC_Certificate_URL_Handler {
/**
* The single instance of the class.
*
* @var HVAC_Certificate_URL_Handler
*/
protected static $_instance = null;
/**
* Main HVAC_Certificate_URL_Handler Instance.
*/
public static function instance() {
if (is_null(self::$_instance)) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Constructor.
*/
public function __construct() {
// Hook very early to catch certificate URLs - before WordPress parses the request
add_action('plugins_loaded', array($this, 'catch_certificate_urls'), 1);
add_action('init', array($this, 'catch_certificate_urls'), 1);
// Also try parse_request as a fallback
add_action('parse_request', array($this, 'parse_certificate_request'), 1);
// Log initialization
if (class_exists('HVAC_Logger')) {
HVAC_Logger::info('Certificate URL Handler initialized', 'Certificates');
}
}
/**
* Catch certificate URLs and handle them directly
*/
public function catch_certificate_urls() {
// Only process on frontend
if (is_admin()) {
return;
}
// Get the request URI
$request_uri = $_SERVER['REQUEST_URI'];
$parsed_url = parse_url($request_uri);
$path = $parsed_url['path'] ?? '';
// Remove any trailing slash for consistency
$path = rtrim($path, '/');
// Check if this is a certificate URL
if (preg_match('#^/hvac-certificate/([a-zA-Z0-9]{32})$#', $path, $matches)) {
$token = $matches[1];
// Log the request
if (class_exists('HVAC_Logger')) {
HVAC_Logger::info("Certificate URL detected - Token: $token", 'Certificates');
}
// Handle the certificate download
$this->handle_certificate_download($token);
exit; // Stop WordPress from processing further
}
}
/**
* Handle certificate download
*/
protected function handle_certificate_download($token) {
// Validate the token
$certificate_data = $this->validate_download_token($token);
if (!$certificate_data) {
if (class_exists('HVAC_Logger')) {
HVAC_Logger::error("Invalid or expired certificate token: $token", 'Certificates');
}
wp_die(__('Invalid or expired certificate download link.', 'hvac-community-events'), 'Certificate Error', array('response' => 404));
}
// Get file path
$file_path = $this->get_certificate_file_path($certificate_data);
if (!$file_path || !file_exists($file_path)) {
if (class_exists('HVAC_Logger')) {
HVAC_Logger::error("Certificate file not found for token: $token", 'Certificates');
}
wp_die(__('Certificate file not found.', 'hvac-community-events'), 'Certificate Error', array('response' => 404));
}
// Log successful access
if (class_exists('HVAC_Logger')) {
HVAC_Logger::info("Serving certificate file: $file_path", 'Certificates');
}
// Serve the file
$this->serve_certificate_file($file_path, $certificate_data);
}
/**
* Validate a certificate download token.
*/
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.
*/
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.
*/
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);
}
/**
* Parse request fallback method
*/
public function parse_certificate_request($wp) {
// Get the request URI
$request_uri = $_SERVER['REQUEST_URI'];
$parsed_url = parse_url($request_uri);
$path = $parsed_url['path'] ?? '';
// Remove any trailing slash for consistency
$path = rtrim($path, '/');
// Check if this is a certificate URL
if (preg_match('#^/hvac-certificate/([a-zA-Z0-9]{32})$#', $path, $matches)) {
$token = $matches[1];
// Log the request
if (class_exists('HVAC_Logger')) {
HVAC_Logger::info("Certificate URL detected via parse_request - Token: $token", 'Certificates');
}
// Handle the certificate download
$this->handle_certificate_download($token);
exit; // Stop WordPress from processing further
}
}
}

View file

@ -64,6 +64,7 @@ class HVAC_Community_Events {
'certificates/class-certificate-settings.php', // Certificate settings 'certificates/class-certificate-settings.php', // Certificate settings
'certificates/class-certificate-template.php', // Certificate template 'certificates/class-certificate-template.php', // Certificate template
'certificates/class-certificate-security.php', // Certificate security 'certificates/class-certificate-security.php', // Certificate security
'certificates/class-certificate-url-handler.php', // Certificate URL handler
'certificates/class-certificate-ajax-handler.php', // Certificate AJAX handling 'certificates/class-certificate-ajax-handler.php', // Certificate AJAX handling
'certificates/class-certificate-fix.php', // Certificate diagnostic/fix tool 'certificates/class-certificate-fix.php', // Certificate diagnostic/fix tool
'community/class-email-debug.php', // Email debugging tools 'community/class-email-debug.php', // Email debugging tools
@ -209,6 +210,11 @@ class HVAC_Community_Events {
HVAC_Certificate_Security::instance(); HVAC_Certificate_Security::instance();
} }
// Initialize certificate URL handler
if (class_exists('HVAC_Certificate_URL_Handler')) {
HVAC_Certificate_URL_Handler::instance();
}
// Add manual flush rewrite rules for admins (debugging) // Add manual flush rewrite rules for admins (debugging)
if (is_admin() && current_user_can('manage_options') && isset($_GET['hvac_flush_rewrite']) && $_GET['hvac_flush_rewrite'] === '1') { if (is_admin() && current_user_can('manage_options') && isset($_GET['hvac_flush_rewrite']) && $_GET['hvac_flush_rewrite'] === '1') {
flush_rewrite_rules(); flush_rewrite_rules();

View file

@ -0,0 +1,167 @@
<?php
/**
* Test Certificate URL System
*
* Run this to test if certificate URLs are working properly
*/
// Load WordPress
require_once __DIR__ . '/../../../wp-load.php';
// Check if user is admin
if (!current_user_can('manage_options')) {
wp_die('Access denied');
}
echo "<h1>Certificate URL System Test</h1>";
echo "<pre>";
// 1. Check if URL handler is loaded
echo "=== URL HANDLER CHECK ===\n";
if (class_exists('HVAC_Certificate_URL_Handler')) {
echo "✅ HVAC_Certificate_URL_Handler class exists\n";
$handler = HVAC_Certificate_URL_Handler::instance();
echo "✅ URL Handler instance created\n";
} else {
echo "❌ HVAC_Certificate_URL_Handler class NOT found!\n";
}
// 2. Check if security class is loaded
echo "\n=== SECURITY CLASS CHECK ===\n";
if (class_exists('HVAC_Certificate_Security')) {
echo "✅ HVAC_Certificate_Security class exists\n";
$security = HVAC_Certificate_Security::instance();
echo "✅ Security instance created\n";
} else {
echo "❌ HVAC_Certificate_Security class NOT found!\n";
}
// 3. Create a test token
echo "\n=== TOKEN GENERATION TEST ===\n";
if (isset($security)) {
$test_data = array(
'file_path' => 'test/certificate.pdf',
'event_name' => 'Test Event',
'attendee_name' => 'Test Attendee',
'certificate_id' => 999
);
$test_url = $security->generate_download_token(999, $test_data, 300); // 5 minute expiry
echo "Generated test URL: $test_url\n";
// Extract token
if (preg_match('/hvac-certificate\/([^\/]+)/', $test_url, $matches)) {
$token = $matches[1];
echo "Token: $token\n";
// Verify token exists in database
$transient = get_transient('hvac_certificate_token_' . $token);
if ($transient) {
echo "✅ Token transient exists\n";
echo "Token data:\n";
print_r($transient);
// Test direct access URL
echo "\n=== DIRECT ACCESS TEST ===\n";
echo "Test this URL in your browser: $test_url\n";
echo "(Should show 'Certificate file not found' since we're using a test file path)\n";
} else {
echo "❌ Token transient NOT found!\n";
}
}
}
// 4. Check hooks
echo "\n=== HOOK REGISTRATION CHECK ===\n";
$init_callbacks = $GLOBALS['wp_filter']['init'] ?? array();
$found_url_handler = false;
foreach ($init_callbacks as $priority => $callbacks) {
foreach ($callbacks as $callback) {
if (is_array($callback['function']) &&
is_object($callback['function'][0]) &&
get_class($callback['function'][0]) === 'HVAC_Certificate_URL_Handler') {
$found_url_handler = true;
echo "✅ URL Handler init hook found at priority: $priority\n";
}
}
}
if (!$found_url_handler) {
echo "❌ URL Handler init hook NOT found!\n";
}
// 5. List recent certificate tokens
echo "\n=== RECENT CERTIFICATE TOKENS ===\n";
global $wpdb;
$recent_tokens = $wpdb->get_results(
"SELECT option_name, option_value
FROM {$wpdb->options}
WHERE option_name LIKE '_transient_hvac_certificate_token_%'
ORDER BY option_id DESC
LIMIT 5"
);
if (empty($recent_tokens)) {
echo "No recent certificate tokens found\n";
} else {
foreach ($recent_tokens as $token_row) {
$token_key = str_replace('_transient_hvac_certificate_token_', '', $token_row->option_name);
echo "\nToken: $token_key\n";
$data = maybe_unserialize($token_row->option_value);
if (is_array($data)) {
echo " URL: " . home_url('hvac-certificate/' . $token_key) . "\n";
echo " Event: " . ($data['event_name'] ?? 'N/A') . "\n";
echo " Attendee: " . ($data['attendee_name'] ?? 'N/A') . "\n";
}
}
}
// 6. Test a real certificate if available
echo "\n=== REAL CERTIFICATE TEST ===\n";
require_once HVAC_CE_PLUGIN_DIR . 'includes/certificates/class-certificate-manager.php';
$cert_manager = HVAC_Certificate_Manager::instance();
// Get a recent certificate
$recent_cert = $wpdb->get_row(
"SELECT * FROM {$wpdb->prefix}hvac_certificates
WHERE file_path IS NOT NULL AND file_path != ''
ORDER BY certificate_id DESC
LIMIT 1"
);
if ($recent_cert) {
echo "Found certificate ID: {$recent_cert->certificate_id}\n";
echo "File path: {$recent_cert->file_path}\n";
// Check if file exists
$upload_dir = wp_upload_dir();
$full_path = $upload_dir['basedir'] . '/' . $recent_cert->file_path;
if (file_exists($full_path)) {
echo "✅ Certificate file exists at: $full_path\n";
// Generate download URL
$cert_data = array(
'certificate_id' => $recent_cert->certificate_id,
'file_path' => $recent_cert->file_path,
'event_name' => 'Test Event',
'attendee_name' => 'Test Attendee'
);
$download_url = $security->generate_download_token($recent_cert->certificate_id, $cert_data, 300);
echo "\nGenerated download URL: $download_url\n";
echo "✅ Test this URL in your browser - it should download the certificate!\n";
} else {
echo "❌ Certificate file NOT found at: $full_path\n";
}
} else {
echo "No certificates with file paths found in database\n";
}
echo "\n=== SUMMARY ===\n";
echo "Certificate URL system should now be working.\n";
echo "URLs in format: " . home_url('hvac-certificate/[token]') . " will be handled directly.\n";
echo "No rewrite rules needed!\n";
echo "</pre>";