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:
parent
29c1d88082
commit
b9ecb37260
4 changed files with 405 additions and 0 deletions
|
|
@ -466,6 +466,25 @@ function 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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,7 @@ class HVAC_Community_Events {
|
|||
'certificates/class-certificate-settings.php', // Certificate settings
|
||||
'certificates/class-certificate-template.php', // Certificate template
|
||||
'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-fix.php', // Certificate diagnostic/fix tool
|
||||
'community/class-email-debug.php', // Email debugging tools
|
||||
|
|
@ -209,6 +210,11 @@ class HVAC_Community_Events {
|
|||
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)
|
||||
if (is_admin() && current_user_can('manage_options') && isset($_GET['hvac_flush_rewrite']) && $_GET['hvac_flush_rewrite'] === '1') {
|
||||
flush_rewrite_rules();
|
||||
|
|
|
|||
|
|
@ -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>";
|
||||
Loading…
Reference in a new issue