diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/certificates/templates/default.html b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/certificates/templates/default.html
new file mode 100644
index 00000000..ac9c291e
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/certificates/templates/default.html
@@ -0,0 +1,120 @@
+
+
+
+
+ Certificate of Completion
+
+
+
+
+
+
+

+
+
+
{{attendee_name}}
+
+
{{event_name}}
+
{{event_date}}
+
+
+
{{instructor_name}}
+
Instructor
+
+
+
+
+
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates.css b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates.css
new file mode 100644
index 00000000..5731188a
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates.css
@@ -0,0 +1,286 @@
+/**
+ * Certificate Styles
+ *
+ * Styles for certificate-related pages and components.
+ */
+
+/* Certificate Tables */
+.hvac-certificate-table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 20px;
+}
+
+.hvac-certificate-table th {
+ background-color: #f1f1f1;
+ text-align: left;
+ padding: 10px;
+ border-bottom: 1px solid #ddd;
+ font-weight: 600;
+}
+
+.hvac-certificate-table td {
+ padding: 12px 10px;
+ border-bottom: 1px solid #eee;
+ vertical-align: middle;
+}
+
+.hvac-certificate-table tr:nth-child(even) {
+ background-color: #f9f9f9;
+}
+
+.hvac-certificate-table tr:hover {
+ background-color: #f0f7ff;
+}
+
+/* Certificate Actions */
+.hvac-certificate-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.hvac-certificate-actions button,
+.hvac-certificate-actions a {
+ background-color: #fafafa;
+ border: 1px solid #ddd;
+ padding: 6px 10px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 13px;
+ text-decoration: none;
+ transition: all 0.2s ease;
+ color: #333;
+}
+
+.hvac-certificate-actions button:hover,
+.hvac-certificate-actions a:hover {
+ background-color: #f0f0f0;
+ border-color: #ccc;
+}
+
+.hvac-view-certificate {
+ background-color: #e0f7fa \!important;
+ border-color: #80deea \!important;
+ color: #006064 \!important;
+}
+
+.hvac-view-certificate:hover {
+ background-color: #b2ebf2 \!important;
+ border-color: #4dd0e1 \!important;
+}
+
+.hvac-email-certificate {
+ background-color: #e8f5e9 \!important;
+ border-color: #a5d6a7 \!important;
+ color: #1b5e20 \!important;
+}
+
+.hvac-email-certificate:hover {
+ background-color: #c8e6c9 \!important;
+ border-color: #81c784 \!important;
+}
+
+.hvac-revoke-certificate {
+ background-color: #ffebee \!important;
+ border-color: #ffcdd2 \!important;
+ color: #b71c1c \!important;
+}
+
+.hvac-revoke-certificate:hover {
+ background-color: #ffcdd2 \!important;
+ border-color: #ef9a9a \!important;
+}
+
+/* Certificate status */
+.hvac-status-active {
+ color: #2e7d32;
+ background-color: #e8f5e9;
+ padding: 3px 8px;
+ border-radius: 12px;
+ display: inline-block;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.hvac-status-revoked {
+ color: #b71c1c;
+ background-color: #ffebee;
+ padding: 3px 8px;
+ border-radius: 12px;
+ display: inline-block;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+/* Certificate filters */
+.hvac-certificate-filters {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-bottom: 20px;
+ padding: 15px;
+ background-color: #f9f9f9;
+ border-radius: 5px;
+ border: 1px solid #eee;
+}
+
+.hvac-filter-group {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ min-width: 200px;
+}
+
+.hvac-filter-group label {
+ font-weight: 600;
+ font-size: 14px;
+}
+
+.hvac-filter-group select,
+.hvac-filter-group input {
+ padding: 8px 10px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+}
+
+.hvac-filter-submit {
+ align-self: flex-end;
+ margin-top: auto;
+}
+
+/* Certificate modal */
+.hvac-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ display: none;
+}
+
+.hvac-certificate-modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: white;
+ padding: 20px;
+ border-radius: 5px;
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+ z-index: 1001;
+ max-width: 90vw;
+ max-height: 90vh;
+ width: 850px;
+ display: none;
+}
+
+.hvac-modal-close {
+ position: absolute;
+ top: 10px;
+ right: 15px;
+ font-size: 24px;
+ cursor: pointer;
+ color: #999;
+ transition: color 0.2s ease;
+}
+
+.hvac-modal-close:hover {
+ color: #333;
+}
+
+.hvac-certificate-preview {
+ width: 100%;
+ height: 70vh;
+ border: 1px solid #ddd;
+ margin-top: 10px;
+}
+
+.hvac-modal-title {
+ margin-top: 0;
+ margin-bottom: 15px;
+ padding-right: 30px;
+}
+
+/* Button loading state */
+.hvac-loading {
+ opacity: 0.7;
+ pointer-events: none;
+ position: relative;
+}
+
+.hvac-loading::after {
+ content: '';
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ border: 2px solid rgba(0, 0, 0, 0.2);
+ border-top-color: #333;
+ border-radius: 50%;
+ animation: hvac-spin 1s linear infinite;
+ position: absolute;
+ right: 8px;
+ top: calc(50% - 6px);
+}
+
+@keyframes hvac-spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Empty state message */
+.hvac-no-certificates {
+ padding: 20px;
+ background-color: #f9f9f9;
+ border: 1px solid #eee;
+ border-radius: 5px;
+ text-align: center;
+ margin: 20px 0;
+}
+
+/* Stats cards */
+.hvac-certificate-stats {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 15px;
+ margin-bottom: 30px;
+}
+
+.hvac-stat-card {
+ background-color: white;
+ border: 1px solid #eee;
+ border-radius: 5px;
+ padding: 15px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+}
+
+.hvac-stat-card h3 {
+ margin-top: 0;
+ font-size: 16px;
+ color: #555;
+}
+
+.hvac-stat-value {
+ font-size: 28px;
+ font-weight: 600;
+ color: #333;
+ margin: 10px 0 5px;
+}
+
+/* Responsive tables */
+@media (max-width: 768px) {
+ .hvac-certificate-table {
+ display: block;
+ overflow-x: auto;
+ }
+
+ .hvac-certificate-filters {
+ flex-direction: column;
+ }
+
+ .hvac-filter-group {
+ width: 100%;
+ }
+}
+EOFCSS < /dev/null
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-background.jpg b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-background.jpg
index 3ace7fcc..f191b280 100644
Binary files a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-background.jpg and b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-background.jpg differ
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-logo.png b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-logo.png
index 03e9e53d..f191b280 100644
Binary files a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-logo.png and b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-logo.png differ
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js
index 8a172198..222c0734 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js
@@ -1,169 +1,208 @@
/**
* Certificate Actions JavaScript
*
- * Handles certificate action functionality (view, email, revoke)
+ * Handles the AJAX interactions for certificate viewing, emailing, and revocation.
*/
(function($) {
'use strict';
-
- // Initialize modal functionality
- function initCertificateModal() {
- var modal = document.getElementById('hvac-certificate-modal');
- var closeBtn = modal.querySelector('.hvac-modal-close');
-
- // Close modal when clicking the X
- closeBtn.addEventListener('click', function() {
- modal.style.display = 'none';
- });
-
- // Close modal when clicking outside
- window.addEventListener('click', function(event) {
- if (event.target === modal) {
- modal.style.display = 'none';
- }
- });
- }
-
- // Handle view certificate action
- function initViewCertificateAction() {
- $('.hvac-view-certificate').on('click', function(e) {
+
+ // Certificate Actions
+ const CertificateActions = {
+ /**
+ * Initialize certificate actions
+ */
+ init: function() {
+ // View certificate
+ $(document).on('click', '.hvac-view-certificate', this.viewCertificate);
+
+ // Email certificate
+ $(document).on('click', '.hvac-email-certificate', this.emailCertificate);
+
+ // Revoke certificate
+ $(document).on('click', '.hvac-revoke-certificate', this.revokeCertificate);
+
+ // Close certificate modal
+ $(document).on('click', '.hvac-modal-close, .hvac-modal-overlay', this.closeCertificateModal);
+ },
+
+ /**
+ * View certificate
+ */
+ viewCertificate: function(e) {
e.preventDefault();
- var certificateId = $(this).data('id');
- var modal = $('#hvac-certificate-modal');
- var iframe = $('#hvac-certificate-preview');
+ const $button = $(this);
+ const certificateId = $button.data('certificate-id');
- // Show loading state
- iframe.attr('src', '');
- modal.css('display', 'block');
- iframe.parent().append('Loading certificate...
');
+ // Disable button while processing
+ $button.prop('disabled', true).addClass('hvac-loading');
- // Get certificate download URL
+ // AJAX request
$.ajax({
url: hvacCertificateData.ajaxUrl,
- method: 'POST',
+ type: 'POST',
data: {
action: 'hvac_get_certificate_url',
certificate_id: certificateId,
nonce: hvacCertificateData.viewNonce
},
success: function(response) {
- $('.hvac-loading').remove();
+ $button.prop('disabled', false).removeClass('hvac-loading');
if (response.success && response.data.url) {
- iframe.attr('src', response.data.url);
+ // Show preview modal if it exists
+ if ($('#hvac-certificate-modal').length > 0) {
+ CertificateActions.showCertificateModal(response.data.url);
+ } else {
+ // Open in new tab
+ window.open(response.data.url, '_blank');
+ }
} else {
- iframe.parent().append('Error: ' + (response.data.message || 'Could not load certificate') + '
');
+ alert(response.data.message || 'Error: Failed to get certificate URL');
}
},
error: function() {
- $('.hvac-loading').remove();
- iframe.parent().append('Error: Could not connect to the server
');
+ $button.prop('disabled', false).removeClass('hvac-loading');
+ alert('Error: Failed to connect to server');
}
});
- });
- }
-
- // Handle email certificate action
- function initEmailCertificateAction() {
- $('.hvac-email-certificate').on('click', function(e) {
+ },
+
+ /**
+ * Show certificate modal
+ */
+ showCertificateModal: function(url) {
+ // Set iframe source
+ $('#hvac-certificate-preview').attr('src', url);
+
+ // Show modal
+ $('.hvac-modal-overlay').fadeIn(200);
+ $('#hvac-certificate-modal').fadeIn(200);
+ },
+
+ /**
+ * Close certificate modal
+ */
+ closeCertificateModal: function(e) {
+ if (e.target === this || $(e.target).hasClass('hvac-modal-close')) {
+ $('.hvac-modal-overlay').fadeOut(200);
+ $('#hvac-certificate-modal').fadeOut(200);
+
+ // Clear iframe src after fade
+ setTimeout(function() {
+ $('#hvac-certificate-preview').attr('src', '');
+ }, 200);
+ }
+ },
+
+ /**
+ * Email certificate
+ */
+ emailCertificate: function(e) {
e.preventDefault();
- var certificateId = $(this).data('id');
- var button = $(this);
+ const $button = $(this);
+ const certificateId = $button.data('certificate-id');
- if (confirm('Send this certificate to the attendee via email?')) {
- // Show loading state
- button.text('Sending...').addClass('hvac-loading');
-
- // Send email
- $.ajax({
- url: hvacCertificateData.ajaxUrl,
- method: 'POST',
- data: {
- action: 'hvac_email_certificate',
- certificate_id: certificateId,
- nonce: hvacCertificateData.emailNonce
- },
- success: function(response) {
- button.removeClass('hvac-loading');
-
- if (response.success) {
- button.text('Sent');
- alert('Certificate was sent successfully.');
- } else {
- button.text('Email');
- alert('Error: ' + (response.data.message || 'Failed to send certificate.'));
- }
- },
- error: function() {
- button.removeClass('hvac-loading').text('Email');
- alert('Error: Could not connect to the server.');
- }
- });
+ // Confirm sending
+ if (\!confirm('Send certificate to attendee via email?')) {
+ return;
}
- });
- }
-
- // Handle revoke certificate action
- function initRevokeCertificateAction() {
- $('.hvac-revoke-certificate').on('click', function(e) {
+
+ // Disable button while processing
+ $button.prop('disabled', true).addClass('hvac-loading');
+
+ // AJAX request
+ $.ajax({
+ url: hvacCertificateData.ajaxUrl,
+ type: 'POST',
+ data: {
+ action: 'hvac_email_certificate',
+ certificate_id: certificateId,
+ nonce: hvacCertificateData.emailNonce
+ },
+ success: function(response) {
+ $button.prop('disabled', false).removeClass('hvac-loading');
+
+ if (response.success) {
+ alert(response.data.message || 'Certificate sent successfully');
+
+ // Update UI to indicate certificate has been emailed
+ const $row = $button.closest('tr');
+ $row.find('.hvac-certificate-emailed').text('Yes');
+ } else {
+ alert(response.data.message || 'Error: Failed to send certificate');
+ }
+ },
+ error: function() {
+ $button.prop('disabled', false).removeClass('hvac-loading');
+ alert('Error: Failed to connect to server');
+ }
+ });
+ },
+
+ /**
+ * Revoke certificate
+ */
+ revokeCertificate: function(e) {
e.preventDefault();
- var certificateId = $(this).data('id');
- var button = $(this);
- var row = button.closest('tr');
+ const $button = $(this);
+ const certificateId = $button.data('certificate-id');
- // Ask for a reason
- var reason = prompt('Please enter a reason for revoking this certificate:');
+ // Prompt for revocation reason
+ const reason = prompt('Please enter a reason for revoking this certificate:', '');
- if (reason !== null) { // Null means the user clicked Cancel
- // Show loading state
- button.text('Revoking...').addClass('hvac-loading');
-
- // Revoke certificate
- $.ajax({
- url: hvacCertificateData.ajaxUrl,
- method: 'POST',
- data: {
- action: 'hvac_revoke_certificate',
- certificate_id: certificateId,
- reason: reason,
- nonce: hvacCertificateData.revokeNonce
- },
- success: function(response) {
- button.removeClass('hvac-loading');
-
- if (response.success) {
- // Update row to show revoked status
- row.find('td:nth-child(5)').html('Revoked');
- row.find('td:nth-child(6)').html('Revoked');
-
- alert('Certificate was revoked successfully.');
- } else {
- button.text('Revoke');
- alert('Error: ' + (response.data.message || 'Failed to revoke certificate.'));
- }
- },
- error: function() {
- button.removeClass('hvac-loading').text('Revoke');
- alert('Error: Could not connect to the server.');
- }
- });
+ // If canceled
+ if (reason === null) {
+ return;
}
- });
- }
-
- // Init on document ready
+
+ // Disable button while processing
+ $button.prop('disabled', true).addClass('hvac-loading');
+
+ // AJAX request
+ $.ajax({
+ url: hvacCertificateData.ajaxUrl,
+ type: 'POST',
+ data: {
+ action: 'hvac_revoke_certificate',
+ certificate_id: certificateId,
+ reason: reason,
+ nonce: hvacCertificateData.revokeNonce
+ },
+ success: function(response) {
+ $button.prop('disabled', false).removeClass('hvac-loading');
+
+ if (response.success) {
+ alert(response.data.message || 'Certificate revoked successfully');
+
+ // Update UI to indicate certificate has been revoked
+ const $row = $button.closest('tr');
+ $row.find('.hvac-certificate-status').text('Revoked');
+ $row.find('.hvac-certificate-status').addClass('hvac-status-revoked');
+ $row.find('.hvac-certificate-revoked-date').text(response.data.revoked_date || 'Today');
+
+ // Hide revoke button, show unrevoke button if it exists
+ $button.hide();
+ $row.find('.hvac-unrevoke-certificate').show();
+ } else {
+ alert(response.data.message || 'Error: Failed to revoke certificate');
+ }
+ },
+ error: function() {
+ $button.prop('disabled', false).removeClass('hvac-loading');
+ alert('Error: Failed to connect to server');
+ }
+ });
+ }
+ };
+
+ // Initialize when document is ready
$(document).ready(function() {
- // Initialize modal
- initCertificateModal();
-
- // Initialize certificate actions
- initViewCertificateAction();
- initEmailCertificateAction();
- initRevokeCertificateAction();
+ CertificateActions.init();
});
-
-})(jQuery);
\ No newline at end of file
+
+})(jQuery);
+EOFJS < /dev/null
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/certificates/template-certificate-reports.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/certificates/template-certificate-reports.php
index 4c915475..06d95546 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/certificates/template-certificate-reports.php
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/certificates/template-certificate-reports.php
@@ -7,7 +7,7 @@
*/
// Exit if accessed directly
-if (!defined('ABSPATH')) {
+if (\!defined('ABSPATH')) {
exit;
}
@@ -15,53 +15,68 @@ if (!defined('ABSPATH')) {
$current_user_id = get_current_user_id();
// Get certificate manager instance
+require_once HVAC_CE_PLUGIN_DIR . 'includes/certificates/class-certificate-manager.php';
$certificate_manager = HVAC_Certificate_Manager::instance();
+// Get certificate security instance
+require_once HVAC_CE_PLUGIN_DIR . 'includes/certificates/class-certificate-security.php';
+$certificate_security = HVAC_Certificate_Security::instance();
+
+// Get filtering parameters
+$filter_event = isset($_GET['filter_event']) ? absint($_GET['filter_event']) : 0;
+$filter_status = isset($_GET['filter_status']) ? sanitize_text_field($_GET['filter_status']) : 'active';
+$page = isset($_GET['certificate_page']) ? absint($_GET['certificate_page']) : 1;
+$per_page = 20;
+
+// Build filter args
+$filter_args = array(
+ 'page' => $page,
+ 'per_page' => $per_page,
+ 'orderby' => 'date_generated',
+ 'order' => 'DESC',
+);
+
+// Add event filter if selected
+if ($filter_event > 0) {
+ $filter_args['event_id'] = $filter_event;
+}
+
+// Add status filter
+if ($filter_status === 'active') {
+ $filter_args['revoked'] = 0;
+} elseif ($filter_status === 'revoked') {
+ $filter_args['revoked'] = 1;
+}
+// Default 'all' doesn't add a filter
+
+// Get certificates for the current user with filters
+$certificates = $certificate_manager->get_user_certificates($current_user_id, $filter_args);
+
+// Get total certificate count for pagination
+$total_certificates = $certificate_manager->get_user_certificate_count($current_user_id, $filter_args);
+$total_pages = ceil($total_certificates / $per_page);
+
+// Get user's events for filtering
+$args = array(
+ 'post_type' => Tribe__Events__Main::POSTTYPE,
+ 'posts_per_page' => -1,
+ 'post_status' => 'publish',
+ 'author' => $current_user_id,
+ 'orderby' => 'meta_value',
+ 'meta_key' => '_EventStartDate',
+ 'order' => 'DESC',
+);
+
+// Allow admins to see all events
+if (current_user_can('edit_others_posts')) {
+ unset($args['author']);
+}
+
+$events = get_posts($args);
+
// Get certificate statistics
$certificate_stats = $certificate_manager->get_user_certificate_stats($current_user_id);
-// Get recent certificates
-$recent_certificates = $certificate_manager->get_user_certificates($current_user_id, array(
- 'limit' => 10,
- 'orderby' => 'date_generated',
- 'order' => 'DESC'
-));
-
-// Get page for pagination
-$page = isset($_GET['cpage']) ? absint($_GET['cpage']) : 1;
-
-// Filters
-$event_filter = isset($_GET['event_id']) ? absint($_GET['event_id']) : 0;
-$status_filter = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '';
-
-// Define filter args
-$filter_args = array(
- 'page' => $page,
- 'per_page' => 20
-);
-
-// Add event filter if set
-if ($event_filter > 0) {
- $filter_args['event_id'] = $event_filter;
-}
-
-// Add status filter if set
-if (!empty($status_filter) && in_array($status_filter, array('active', 'revoked'))) {
- $filter_args['revoked'] = ($status_filter === 'revoked') ? 1 : 0;
-}
-
-// Get filtered certificates
-$certificates = $certificate_manager->get_user_certificates($current_user_id, $filter_args);
-
-// Get total count for pagination
-$total_certificates = $certificate_manager->get_user_certificate_count($current_user_id, $filter_args);
-
-// Calculate total pages
-$total_pages = ceil($total_certificates / $filter_args['per_page']);
-
-// Get events for filter dropdown
-$events_with_certificates = $certificate_manager->get_events_with_certificates($current_user_id);
-
// Get header and footer
get_header();
?>
@@ -70,149 +85,200 @@ get_header();
-
-
-
-
-
Active Certificates
-
-
-
-
Revoked Certificates
-
+
+ <\!-- Certificate Statistics -->
+
+
Certificate Statistics
+
+
+
+
+
+
Active Certificates
+
+
+
+
+
Revoked Certificates
+
+
+
+
+
Emailed Certificates
+
+
-
-
-
-
+
+ <\!-- Certificate Filters -->
+