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');
|
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.
|
||||||
|
|
|
||||||
|
|
@ -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-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();
|
||||||
|
|
|
||||||
|
|
@ -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