diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates-admin.css b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates-admin.css
new file mode 100644
index 00000000..b81a5684
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-certificates-admin.css
@@ -0,0 +1,169 @@
+/**
+ * Certificate Admin Styles
+ *
+ * Styles for the certificate management pages.
+ */
+
+/* Certificate preview container */
+.hvac-certificate-preview {
+ border: 1px solid #ddd;
+ background-color: #f9f9f9;
+ padding: 20px;
+ margin-bottom: 20px;
+ text-align: center;
+ position: relative;
+}
+
+.hvac-certificate-preview img {
+ max-width: 100%;
+ height: auto;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+/* Certificate settings form */
+.hvac-certificate-settings {
+ max-width: 800px;
+ margin-bottom: 30px;
+}
+
+.hvac-certificate-settings .form-table th {
+ width: 200px;
+}
+
+.hvac-certificate-settings .color-picker {
+ width: 80px;
+}
+
+.hvac-certificate-placeholder-list {
+ background: #f5f5f5;
+ padding: 10px 15px;
+ border-left: 4px solid #0074be;
+ margin-bottom: 20px;
+}
+
+.hvac-certificate-placeholder-list code {
+ margin-right: 10px;
+ background: #fff;
+ padding: 2px 4px;
+}
+
+/* Certificate generation button */
+.hvac-generate-certificate-btn {
+ background-color: #0074be;
+ color: #fff;
+ border: none;
+ padding: 10px 15px;
+ cursor: pointer;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ border-radius: 3px;
+}
+
+.hvac-generate-certificate-btn:hover {
+ background-color: #005fa0;
+}
+
+/* Certificate statistics */
+.hvac-certificate-stats {
+ display: flex;
+ margin-bottom: 20px;
+}
+
+.hvac-certificate-stat-box {
+ flex: 1;
+ padding: 15px;
+ margin-right: 15px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-left: 5px solid;
+ text-align: center;
+}
+
+.hvac-certificate-stat-box:last-child {
+ margin-right: 0;
+}
+
+.hvac-certificate-stat-box.active {
+ border-left-color: #46b450;
+}
+
+.hvac-certificate-stat-box.revoked {
+ border-left-color: #dc3232;
+}
+
+.hvac-certificate-stat-box.total {
+ border-left-color: #0074be;
+}
+
+.hvac-certificate-stat-box h3 {
+ margin-top: 0;
+ color: #555;
+ font-size: 14px;
+ text-transform: uppercase;
+}
+
+.hvac-certificate-stat-box .stat-number {
+ font-size: 24px;
+ font-weight: bold;
+ margin: 10px 0;
+ color: #333;
+}
+
+/* Certificate table */
+.hvac-certificates-table {
+ width: 100%;
+ border-collapse: collapse;
+ border: 1px solid #ddd;
+}
+
+.hvac-certificates-table th,
+.hvac-certificates-table td {
+ padding: 12px 15px;
+ text-align: left;
+ border-bottom: 1px solid #ddd;
+}
+
+.hvac-certificates-table th {
+ background-color: #f5f5f5;
+ font-weight: bold;
+}
+
+.hvac-certificates-table tr:hover {
+ background-color: #f9f9f9;
+}
+
+.hvac-certificates-table .status-active,
+.hvac-certificates-table .status-revoked {
+ padding: 5px 8px;
+ border-radius: 3px;
+ font-size: 12px;
+ font-weight: bold;
+ text-transform: uppercase;
+}
+
+.hvac-certificates-table .status-active {
+ background-color: #e7f9e7;
+ color: #46b450;
+}
+
+.hvac-certificates-table .status-revoked {
+ background-color: #f9e7e7;
+ color: #dc3232;
+}
+
+.hvac-certificates-table .certificate-actions a {
+ margin-right: 10px;
+}
+
+/* Media Queries */
+@media (max-width: 768px) {
+ .hvac-certificate-stats {
+ flex-direction: column;
+ }
+
+ .hvac-certificate-stat-box {
+ margin-right: 0;
+ margin-bottom: 15px;
+ }
+}
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-event-summary.css b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-event-summary.css
index 62fa7197..8580f591 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-event-summary.css
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/css/hvac-event-summary.css
@@ -280,6 +280,112 @@
font-weight: 500;
}
+/* Certificate Actions */
+.hvac-cert-action {
+ display: inline-block;
+ margin-left: 5px;
+ padding: 3px 8px;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ text-decoration: none;
+ color: #ffffff;
+ background-color: var(--hvac-primary, #0073aa);
+ transition: background-color 0.2s ease;
+}
+
+.hvac-cert-action:hover {
+ background-color: var(--hvac-primary-dark, #005a87);
+ color: #ffffff;
+ text-decoration: none;
+}
+
+.hvac-view-certificate {
+ background-color: var(--hvac-secondary, #6c757d);
+}
+
+.hvac-view-certificate:hover {
+ background-color: var(--hvac-secondary-dark, #495057);
+}
+
+.hvac-email-certificate {
+ background-color: var(--hvac-primary, #0073aa);
+}
+
+.hvac-email-certificate:hover {
+ background-color: var(--hvac-primary-dark, #005a87);
+}
+
+.hvac-revoke-certificate {
+ background-color: var(--hvac-danger, #dc3545);
+}
+
+.hvac-revoke-certificate:hover {
+ background-color: var(--hvac-danger-dark, #bd2130);
+}
+
+/* Certificate Modal */
+.hvac-modal {
+ display: none;
+ position: fixed;
+ z-index: 9999;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(0, 0, 0, 0.5);
+}
+
+.hvac-modal-content {
+ background-color: #fefefe;
+ margin: 5% auto;
+ padding: 20px;
+ border: 1px solid #888;
+ width: 80%;
+ max-width: 900px;
+ border-radius: var(--hvac-border-radius, 4px);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+ position: relative;
+}
+
+.hvac-modal-close {
+ color: #aaa;
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+ cursor: pointer;
+ position: absolute;
+ top: 10px;
+ right: 15px;
+}
+
+.hvac-modal-close:hover,
+.hvac-modal-close:focus {
+ color: #000;
+ text-decoration: none;
+}
+
+.hvac-modal-body {
+ padding: 10px 0;
+ min-height: 200px;
+}
+
+.hvac-loading {
+ text-align: center;
+ padding: 20px;
+ font-style: italic;
+ color: #666;
+}
+
+.hvac-error {
+ color: #dc3545;
+ padding: 10px;
+ text-align: center;
+ background-color: #f8d7da;
+ border-radius: 4px;
+ margin: 10px 0;
+}
+
/* Responsive Adjustments */
@media (max-width: 768px) {
.hvac-event-summary-header {
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-background-thumb.jpg b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-background-thumb.jpg
new file mode 100644
index 00000000..d916b63e
Binary files /dev/null and b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/images/certificate-background-thumb.jpg differ
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
new file mode 100644
index 00000000..3ace7fcc
Binary files /dev/null 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
new file mode 100644
index 00000000..03e9e53d
Binary files /dev/null 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
new file mode 100644
index 00000000..8a172198
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-actions.js
@@ -0,0 +1,169 @@
+/**
+ * Certificate Actions JavaScript
+ *
+ * Handles certificate action functionality (view, email, revoke)
+ */
+
+(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) {
+ e.preventDefault();
+
+ var certificateId = $(this).data('id');
+ var modal = $('#hvac-certificate-modal');
+ var iframe = $('#hvac-certificate-preview');
+
+ // Show loading state
+ iframe.attr('src', '');
+ modal.css('display', 'block');
+ iframe.parent().append('
Loading certificate...
');
+
+ // Get certificate download URL
+ $.ajax({
+ url: hvacCertificateData.ajaxUrl,
+ method: 'POST',
+ data: {
+ action: 'hvac_get_certificate_url',
+ certificate_id: certificateId,
+ nonce: hvacCertificateData.viewNonce
+ },
+ success: function(response) {
+ $('.hvac-loading').remove();
+
+ if (response.success && response.data.url) {
+ iframe.attr('src', response.data.url);
+ } else {
+ iframe.parent().append('Error: ' + (response.data.message || 'Could not load certificate') + '
');
+ }
+ },
+ error: function() {
+ $('.hvac-loading').remove();
+ iframe.parent().append('Error: Could not connect to the server
');
+ }
+ });
+ });
+ }
+
+ // Handle email certificate action
+ function initEmailCertificateAction() {
+ $('.hvac-email-certificate').on('click', function(e) {
+ e.preventDefault();
+
+ var certificateId = $(this).data('id');
+ var button = $(this);
+
+ 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.');
+ }
+ });
+ }
+ });
+ }
+
+ // Handle revoke certificate action
+ function initRevokeCertificateAction() {
+ $('.hvac-revoke-certificate').on('click', function(e) {
+ e.preventDefault();
+
+ var certificateId = $(this).data('id');
+ var button = $(this);
+ var row = button.closest('tr');
+
+ // Ask for a reason
+ var 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.');
+ }
+ });
+ }
+ });
+ }
+
+ // Init on document ready
+ $(document).ready(function() {
+ // Initialize modal
+ initCertificateModal();
+
+ // Initialize certificate actions
+ initViewCertificateAction();
+ initEmailCertificateAction();
+ initRevokeCertificateAction();
+ });
+
+})(jQuery);
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-admin.js b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-admin.js
new file mode 100644
index 00000000..4349c969
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-certificate-admin.js
@@ -0,0 +1,209 @@
+/**
+ * Certificate Admin JavaScript
+ *
+ * Handles certificate admin functionality including preview generation.
+ */
+
+(function($) {
+ 'use strict';
+
+ // Initialize color pickers
+ function initColorPickers() {
+ if ($.fn.wpColorPicker) {
+ $('.hvac-color-picker').wpColorPicker({
+ change: function() {
+ // Trigger settings change on color picker change
+ $(this).trigger('change');
+ }
+ });
+ }
+ }
+
+ // Generate certificate preview
+ function generatePreview() {
+ var previewContainer = $('.hvac-certificate-preview');
+ var loadingIndicator = $('Generating preview...
');
+
+ // Show loading indicator
+ previewContainer.html(loadingIndicator);
+
+ // Collect all settings
+ var settings = {};
+ $('.hvac-certificate-settings input, .hvac-certificate-settings select, .hvac-certificate-settings textarea').each(function() {
+ var input = $(this);
+ var name = input.attr('name');
+
+ if (name && name.startsWith('hvac_certificate_')) {
+ var key = name.replace('hvac_certificate_', '');
+ settings[key] = input.val();
+ }
+ });
+
+ // Send AJAX request
+ $.ajax({
+ url: ajaxurl,
+ method: 'POST',
+ data: {
+ action: 'hvac_preview_certificate',
+ nonce: hvacCertificateData.previewNonce,
+ settings: settings
+ },
+ success: function(response) {
+ if (response.success) {
+ // Create preview image
+ var previewImg = $('
', {
+ src: response.data.preview_url,
+ alt: 'Certificate Preview',
+ class: 'hvac-certificate-preview-img'
+ });
+
+ // Display preview
+ previewContainer.html(previewImg);
+ } else {
+ // Show error
+ previewContainer.html('Error: ' + response.data.message + '
');
+ }
+ },
+ error: function() {
+ // Show connection error
+ previewContainer.html('Error: Could not connect to the server
');
+ }
+ });
+ }
+
+ // Handle file uploads
+ function initFileUploads() {
+ // Background image upload
+ $('#hvac_upload_background_btn').on('click', function(e) {
+ e.preventDefault();
+
+ var button = $(this);
+ var field = $('#hvac_certificate_custom_background');
+
+ var frame = wp.media({
+ title: 'Select Certificate Background',
+ button: {
+ text: 'Use this image'
+ },
+ multiple: false
+ });
+
+ frame.on('select', function() {
+ var attachment = frame.state().get('selection').first().toJSON();
+ field.val(attachment.url);
+
+ // Show preview
+ if ($('#background_preview').length) {
+ $('#background_preview').attr('src', attachment.url);
+ } else {
+ $('
', {
+ id: 'background_preview',
+ src: attachment.url,
+ class: 'hvac-image-preview',
+ style: 'max-width: 200px; margin-top: 10px;'
+ }).insertAfter(field);
+ }
+
+ // Trigger change to update preview
+ field.trigger('change');
+ });
+
+ frame.open();
+ });
+
+ // Logo image upload
+ $('#hvac_upload_logo_btn').on('click', function(e) {
+ e.preventDefault();
+
+ var button = $(this);
+ var field = $('#hvac_certificate_custom_logo');
+
+ var frame = wp.media({
+ title: 'Select Certificate Logo',
+ button: {
+ text: 'Use this image'
+ },
+ multiple: false
+ });
+
+ frame.on('select', function() {
+ var attachment = frame.state().get('selection').first().toJSON();
+ field.val(attachment.url);
+
+ // Show preview
+ if ($('#logo_preview').length) {
+ $('#logo_preview').attr('src', attachment.url);
+ } else {
+ $('
', {
+ id: 'logo_preview',
+ src: attachment.url,
+ class: 'hvac-image-preview',
+ style: 'max-width: 100px; margin-top: 10px;'
+ }).insertAfter(field);
+ }
+
+ // Trigger change to update preview
+ field.trigger('change');
+ });
+
+ frame.open();
+ });
+ }
+
+ // Insert placeholder
+ function initPlaceholders() {
+ $('.hvac-placeholder-insert').on('click', function(e) {
+ e.preventDefault();
+
+ var placeholder = $(this).data('placeholder');
+ var targetField = $('#hvac_certificate_completion_text');
+
+ // Insert at cursor position
+ var field = targetField[0];
+ var startPos = field.selectionStart;
+ var endPos = field.selectionEnd;
+ var text = field.value;
+
+ field.value = text.substring(0, startPos) + placeholder + text.substring(endPos);
+
+ // Set cursor position after placeholder
+ field.selectionStart = startPos + placeholder.length;
+ field.selectionEnd = startPos + placeholder.length;
+
+ // Focus the field
+ field.focus();
+
+ // Trigger change to update preview
+ targetField.trigger('change');
+ });
+ }
+
+ // Init on document ready
+ $(document).ready(function() {
+ // Initialize color pickers
+ initColorPickers();
+
+ // Initialize file uploads
+ initFileUploads();
+
+ // Initialize placeholder inserts
+ initPlaceholders();
+
+ // Generate preview on settings change (debounced)
+ var previewTimeout;
+ $('.hvac-certificate-settings input, .hvac-certificate-settings select, .hvac-certificate-settings textarea').on('change', function() {
+ clearTimeout(previewTimeout);
+ previewTimeout = setTimeout(generatePreview, 800);
+ });
+
+ // Generate initial preview
+ generatePreview();
+
+ // Handle preview button click
+ $('#hvac_generate_preview_btn').on('click', function(e) {
+ e.preventDefault();
+ generatePreview();
+ });
+ });
+
+})(jQuery);
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-event-summary.js b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-event-summary.js
new file mode 100644
index 00000000..f3c936a5
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/assets/js/hvac-event-summary.js
@@ -0,0 +1,181 @@
+/**
+ * Event Summary Certificate Actions JavaScript
+ *
+ * Handles certificate actions for the Event Summary page
+ */
+
+(function($) {
+ 'use strict';
+
+ // Initialize certificate actions when document is ready
+ $(document).ready(function() {
+ // Set up certificate action modal
+ initCertificateModal();
+
+ // Set up certificate action handlers
+ initCertificateActions();
+ });
+
+ // Initialize certificate modal
+ function initCertificateModal() {
+ // Check if modal exists, create it if not
+ if ($('#hvac-certificate-modal').length === 0) {
+ var modalHtml = `
+
+ `;
+ $('body').append(modalHtml);
+
+ // Add modal close functionality
+ $('.hvac-modal-close').on('click', function() {
+ $('#hvac-certificate-modal').hide();
+ });
+
+ // Close modal when clicking outside
+ $(window).on('click', function(event) {
+ if ($(event.target).is('#hvac-certificate-modal')) {
+ $('#hvac-certificate-modal').hide();
+ }
+ });
+ }
+ }
+
+ // Initialize certificate action handlers
+ function initCertificateActions() {
+ // View certificate action
+ $('.hvac-view-certificate').on('click', function(e) {
+ e.preventDefault();
+
+ var eventId = $(this).data('event');
+ var attendeeId = $(this).data('attendee');
+
+ // Show modal with loading indicator
+ var modal = $('#hvac-certificate-modal');
+ var iframe = $('#hvac-certificate-preview');
+ iframe.attr('src', '');
+
+ var loadingHtml = 'Loading certificate...
';
+ $('.hvac-modal-body').append(loadingHtml);
+ modal.show();
+
+ // Fetch certificate URL via AJAX
+ $.ajax({
+ url: hvacEventSummary.ajaxUrl,
+ method: 'POST',
+ data: {
+ action: 'hvac_get_certificate_url',
+ event_id: eventId,
+ attendee_id: attendeeId,
+ nonce: hvacEventSummary.certificateNonce
+ },
+ success: function(response) {
+ $('.hvac-loading').remove();
+
+ if (response.success && response.data.url) {
+ iframe.attr('src', response.data.url);
+ } else {
+ $('.hvac-modal-body').append('Error: ' + (response.data.message || 'Could not load certificate') + '
');
+ }
+ },
+ error: function() {
+ $('.hvac-loading').remove();
+ $('.hvac-modal-body').append('Error: Could not connect to the server
');
+ }
+ });
+ });
+
+ // Email certificate action
+ $('.hvac-email-certificate').on('click', function(e) {
+ e.preventDefault();
+
+ var eventId = $(this).data('event');
+ var attendeeId = $(this).data('attendee');
+ var button = $(this);
+
+ if (confirm('Send this certificate to the attendee via email?')) {
+ // Show loading state
+ button.text('Sending...').addClass('hvac-loading');
+
+ // Send email via AJAX
+ $.ajax({
+ url: hvacEventSummary.ajaxUrl,
+ method: 'POST',
+ data: {
+ action: 'hvac_email_certificate',
+ event_id: eventId,
+ attendee_id: attendeeId,
+ nonce: hvacEventSummary.certificateNonce
+ },
+ success: function(response) {
+ button.removeClass('hvac-loading');
+
+ if (response.success) {
+ button.closest('td').find('.certificate-status').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.');
+ }
+ });
+ }
+ });
+
+ // Revoke certificate action
+ $('.hvac-revoke-certificate').on('click', function(e) {
+ e.preventDefault();
+
+ var eventId = $(this).data('event');
+ var attendeeId = $(this).data('attendee');
+ var button = $(this);
+ var cell = button.closest('td');
+
+ // Ask for a reason
+ var 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 via AJAX
+ $.ajax({
+ url: hvacEventSummary.ajaxUrl,
+ method: 'POST',
+ data: {
+ action: 'hvac_revoke_certificate',
+ event_id: eventId,
+ attendee_id: attendeeId,
+ reason: reason,
+ nonce: hvacEventSummary.certificateNonce
+ },
+ success: function(response) {
+ button.removeClass('hvac-loading');
+
+ if (response.success) {
+ // Update cell content to show revoked status
+ cell.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.');
+ }
+ });
+ }
+ });
+ }
+})(jQuery);
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/composer.json b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/composer.json
index 7da209ec..c7b18aee 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/composer.json
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/composer.json
@@ -4,7 +4,8 @@
"type": "wordpress-plugin",
"require": {
"php": ">=7.4",
- "composer/installers": "^1.0"
+ "composer/installers": "^1.0",
+ "tecnickcom/tcpdf": "^6.6"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/docs/certificate-generation-plan.md b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/docs/certificate-generation-plan.md
new file mode 100644
index 00000000..94b98b04
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/docs/certificate-generation-plan.md
@@ -0,0 +1,280 @@
+# Certificate Generation Implementation Plan
+
+## Overview
+This document outlines the development plan for implementing the certificate generation feature for the HVAC Community Events plugin. This feature is part of Phase 3 and will enable trainers to create, manage, and distribute certificates to event attendees.
+
+## 1. Feature Architecture
+
+### 1.1 Database Schema
+
+We will create a new table in the WordPress database called `{prefix}_hvac_certificates` with the following structure:
+
+```sql
+CREATE TABLE {prefix}_hvac_certificates (
+ certificate_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ event_id BIGINT(20) UNSIGNED NOT NULL,
+ attendee_id BIGINT(20) UNSIGNED NOT NULL,
+ user_id BIGINT(20) UNSIGNED NOT NULL,
+ certificate_number VARCHAR(50) NOT NULL,
+ file_path VARCHAR(255) NOT NULL,
+ date_generated DATETIME NOT NULL,
+ generated_by BIGINT(20) UNSIGNED NOT NULL,
+ revoked TINYINT(1) NOT NULL DEFAULT 0,
+ revoked_date DATETIME DEFAULT NULL,
+ revoked_by BIGINT(20) UNSIGNED DEFAULT NULL,
+ revoked_reason TEXT DEFAULT NULL,
+ email_sent TINYINT(1) NOT NULL DEFAULT 0,
+ email_sent_date DATETIME DEFAULT NULL,
+ PRIMARY KEY (certificate_id),
+ UNIQUE KEY event_attendee (event_id, attendee_id),
+ KEY event_id (event_id),
+ KEY attendee_id (attendee_id),
+ KEY user_id (user_id),
+ KEY certificate_number (certificate_number),
+ KEY revoked (revoked)
+) {charset_collate};
+```
+
+We will also need options to store certificate generation settings:
+
+```php
+// Certificate options
+update_option('hvac_certificate_counter', 0); // For generating sequential certificate numbers
+update_option('hvac_certificate_prefix', 'HVAC-'); // Prefix for certificate numbers
+update_option('hvac_certificate_storage_path', 'certificates'); // Relative to wp-content/uploads/
+```
+
+### 1.2 File Structure
+
+```
+hvac-community-events/
+├── assets/
+│ ├── css/
+│ │ ├── hvac-certificates-report.css
+│ │ └── hvac-generate-certificates.css
+│ ├── js/
+│ │ ├── hvac-certificates-report.js
+│ │ └── hvac-generate-certificates.js
+│ └── templates/
+│ └── certificate-template.pdf
+├── includes/
+│ ├── certificates/
+│ │ ├── class-certificate-manager.php
+│ │ ├── class-certificate-generator.php
+│ │ ├── class-certificates-report.php
+│ │ └── class-certificates-settings.php
+│ └── community/
+│ ├── class-certificates-report-page.php
+│ └── class-generate-certificates-page.php
+├── templates/
+│ ├── template-certificates-report.php
+│ └── template-generate-certificates.php
+```
+
+### 1.3 Class Structure
+
+#### HVAC_Certificate_Manager
+- Main controller class for certificate operations
+- Manages database interactions
+- Handles certificate status updates
+
+#### HVAC_Certificate_Generator
+- Handles PDF generation
+- Manages certificate templates
+- Creates individualized certificates
+
+#### HVAC_Certificates_Report
+- Data handler for the Certificates Report page
+- Retrieves statistics and certificate data
+
+#### HVAC_Certificates_Settings
+- Manages certificate settings
+- Handles template customization options
+
+#### HVAC_Certificates_Report_Page
+- Controller for the Certificates Report page
+- Registers shortcode and handles rendering
+
+#### HVAC_Generate_Certificates_Page
+- Controller for the Generate Certificates page
+- Manages multi-step workflow
+
+## 2. Implementation Steps
+
+### 2.1 Setup Phase
+
+1. **Add PDF Library**
+ - Add TCPDF or mPDF library to composer.json
+ - Update composer dependencies
+ - Create base certificate template
+
+2. **Create Database Tables**
+ - Implement activation hook for table creation
+ - Create upgrade routine for existing installations
+
+3. **Create Certificate Management Classes**
+ - Implement Certificate_Manager class
+ - Implement Certificate_Generator class
+ - Add certificate settings management
+
+### 2.2 UI Development Phase
+
+4. **Develop Certificate Report Page**
+ - Create page template with statistics section
+ - Implement certificates table with filtering
+ - Add navigation and action buttons
+
+5. **Develop Generate Certificates Page**
+ - Implement multi-step form UI
+ - Create event selection interface
+ - Create attendee selection interface
+ - Add certificate preview functionality
+ - Implement distribution options
+
+6. **Integrate with Existing Pages**
+ - Add certificate actions to Event Summary page
+ - Add certificate indicators to Dashboard
+
+### 2.3 Functionality Development Phase
+
+7. **Implement Certificate Generation**
+ - Create PDF generation functionality
+ - Implement certificate numbering system
+ - Add storage and retrieval mechanisms
+
+8. **Implement Email Distribution**
+ - Create email template for certificates
+ - Add batch email functionality
+ - Implement email tracking
+
+9. **Add Revocation Functionality**
+ - Create certificate revocation UI
+ - Implement revocation status tracking
+ - Add revocation reason documentation
+
+### 2.4 Testing and Finalization Phase
+
+10. **Create Automated Tests**
+ - Add unit tests for certificate classes
+ - Implement E2E tests for certificate workflow
+ - Test cross-browser compatibility
+
+11. **Finalize Documentation**
+ - Update user documentation
+ - Create admin documentation
+ - Add code comments and docblocks
+
+12. **Security and Performance Optimization**
+ - Add proper capability checks
+ - Implement file access security
+ - Optimize database queries
+ - Add caching where appropriate
+
+## 3. PDF Library Selection
+
+After reviewing available PHP PDF libraries, we recommend using TCPDF for the following reasons:
+
+1. **WordPress Compatibility**: TCPDF works well with WordPress environments
+2. **Feature Set**: Supports all required features for certificates (text, images, signatures)
+3. **Active Maintenance**: Regular updates and security patches
+4. **License**: Compatible with GPL-2.0+
+5. **Performance**: Efficient for generating certificate PDFs
+
+Alternatives considered:
+- FPDF: Simpler but fewer features
+- mPDF: HTML to PDF conversion (more complex but more flexible)
+- Dompdf: HTML to PDF conversion (good for complex layouts)
+
+## 4. Certificate Design
+
+The certificate template will include:
+
+1. **Header**: HVAC Community Events logo and title
+2. **Main Content**:
+ - "Certificate of Completion" title
+ - Attendee name (large, prominent font)
+ - Course completion statement
+ - Event name and date
+ - Training organization name
+ - Venue location
+
+3. **Footer**:
+ - Instructor name and signature
+ - Certificate number
+ - Date of issuance
+ - QR code linking to verification page (future enhancement)
+
+## 5. Integration Points
+
+### 5.1 The Events Calendar Integration
+- Pull event details (name, date, venue)
+- Access organizer information
+
+### 5.2 Event Tickets Integration
+- Access attendee data
+- Check attendance status from check-in feature
+
+### 5.3 WordPress Integration
+- User authentication and capabilities
+- File storage in wp-content/uploads
+
+## 6. Timeline and Dependencies
+
+| Task | Dependencies | Estimated Time |
+|------|--------------|----------------|
+| Database Setup | None | 1 day |
+| PDF Library Integration | None | 1 day |
+| Certificate Template Design | PDF Library | 2 days |
+| Certificate Manager Class | Database Setup | 2 days |
+| Certificates Report Page | Certificate Manager | 3 days |
+| Generate Certificates Page | Certificate Manager, Template | 4 days |
+| Email Integration | Generate Certificates Page | 2 days |
+| Event Summary Integration | Certificate Manager | 1 day |
+| Testing and Bug Fixes | All Above | 3 days |
+| Documentation | All Above | 1 day |
+
+Total estimated time: 20 days
+
+## 7. Potential Challenges and Solutions
+
+### 7.1 PDF Generation Performance
+**Challenge**: Generating multiple PDFs could be resource-intensive
+**Solution**:
+- Implement batch processing
+- Add progress indicators
+- Use asynchronous processing for large batches
+
+### 7.2 File Storage and Security
+**Challenge**: Certificate PDFs need secure storage but must be accessible
+**Solution**:
+- Store in protected directory with .htaccess rules
+- Implement secure download mechanism
+- Use WordPress nonces for download links
+
+### 7.3 Email Deliverability
+**Challenge**: Bulk sending certificates could trigger spam filters
+**Solution**:
+- Use WordPress mail with proper headers
+- Implement rate limiting
+- Add configurable batch sizes
+
+## 8. Future Enhancements
+
+1. **Certificate Verification System**
+ - Public verification page using certificate number
+ - QR codes on certificates linking to verification
+
+2. **Advanced Certificate Templates**
+ - Multiple template options
+ - Custom branding for trainers
+
+3. **Integration with My Training Page**
+ - Allow trainees to access their certificates from profile
+
+4. **Certificate Analytics**
+ - Track certificate views and downloads
+ - Generate reports on certificate usage
+
+## 9. Conclusion
+
+This implementation plan provides a comprehensive approach to developing the certificate generation feature for the HVAC Community Events plugin. By following this structured approach, we can deliver a robust, secure, and user-friendly certificate system that enhances the value of the training platform for both trainers and attendees.
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/hvac-community-events.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/hvac-community-events.php
index 6400c6ae..e1d5a96f 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/hvac-community-events.php
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/hvac-community-events.php
@@ -67,6 +67,14 @@ function hvac_ce_create_required_pages() {
'title' => 'Email Attendees',
'content' => '[hvac_email_attendees]',
],
+ 'certificate-reports' => [ // Add certificate reports page
+ 'title' => 'Certificate Reports',
+ 'content' => '[hvac_certificate_reports]',
+ ],
+ 'generate-certificates' => [ // Add generate certificates page
+ 'title' => 'Generate Certificates',
+ 'content' => '[hvac_generate_certificates]',
+ ],
// REMOVED: 'submit-event' page creation. Will link to default TEC CE page.
// 'submit-event' => [
// 'title' => 'Submit Event',
@@ -275,6 +283,50 @@ function hvac_ce_enqueue_common_assets() {
['hvac-common-style'], // Depends on common styles
HVAC_CE_VERSION
);
+
+ // Enqueue event summary JS for certificate actions
+ wp_enqueue_script(
+ 'hvac-event-summary-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-event-summary.js',
+ ['jquery'], // jQuery dependency
+ HVAC_CE_VERSION,
+ true // Load in footer
+ );
+
+ // Localize script with AJAX data
+ wp_localize_script('hvac-event-summary-js', 'hvacEventSummary', [
+ 'ajaxUrl' => admin_url('admin-ajax.php'),
+ 'certificateNonce' => wp_create_nonce('hvac_certificate_actions')
+ ]);
+ }
+
+ // Enqueue certificate-related styles
+ if (is_page('certificate-reports') || is_page('generate-certificates')) {
+ wp_enqueue_style(
+ 'hvac-certificates-admin-style',
+ HVAC_CE_PLUGIN_URL . 'assets/css/hvac-certificates-admin.css',
+ ['hvac-common-style'], // Depends on common styles
+ HVAC_CE_VERSION
+ );
+
+ // Enqueue certificate JS
+ wp_enqueue_script(
+ 'hvac-certificate-admin-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-certificate-admin.js',
+ ['jquery', 'wp-color-picker'], // jQuery dependency
+ HVAC_CE_VERSION,
+ true // Load in footer
+ );
+
+ // Add WordPress color picker if needed
+ wp_enqueue_style('wp-color-picker');
+ wp_enqueue_script('wp-color-picker');
+
+ // Localize script with AJAX data
+ wp_localize_script('hvac-certificate-admin-js', 'hvacCertificateData', [
+ 'ajaxUrl' => admin_url('admin-ajax.php'),
+ 'previewNonce' => wp_create_nonce('hvac_certificate_preview')
+ ]);
}
}
add_action('wp_enqueue_scripts', 'hvac_ce_enqueue_common_assets');
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php
new file mode 100644
index 00000000..e4457eb9
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-ajax-handler.php
@@ -0,0 +1,358 @@
+certificate_manager = HVAC_Certificate_Manager::instance();
+ $this->certificate_security = HVAC_Certificate_Security::instance();
+
+ // Initialize hooks
+ $this->init_hooks();
+ }
+
+ /**
+ * Initialize hooks.
+ */
+ protected function init_hooks() {
+ // Register AJAX handlers
+ add_action('wp_ajax_hvac_get_certificate_url', array($this, 'get_certificate_url'));
+ add_action('wp_ajax_hvac_email_certificate', array($this, 'email_certificate'));
+ add_action('wp_ajax_hvac_revoke_certificate', array($this, 'revoke_certificate'));
+
+ // Enqueue scripts
+ add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
+ }
+
+ /**
+ * Enqueue scripts and localize data.
+ */
+ public function enqueue_scripts() {
+ // Only load on certificate pages
+ if (is_page('certificate-reports')) {
+ // Enqueue certificate actions JS
+ wp_enqueue_script(
+ 'hvac-certificate-actions-js',
+ HVAC_CE_PLUGIN_URL . 'assets/js/hvac-certificate-actions.js',
+ array('jquery'),
+ HVAC_CE_VERSION,
+ true
+ );
+
+ // Localize script with AJAX data
+ wp_localize_script('hvac-certificate-actions-js', 'hvacCertificateData', array(
+ 'ajaxUrl' => admin_url('admin-ajax.php'),
+ 'viewNonce' => wp_create_nonce('hvac_view_certificate'),
+ 'emailNonce' => wp_create_nonce('hvac_email_certificate'),
+ 'revokeNonce' => wp_create_nonce('hvac_revoke_certificate')
+ ));
+ }
+ }
+
+ /**
+ * AJAX handler for getting a certificate download URL.
+ */
+ public function get_certificate_url() {
+ // Verify nonce
+ if (
+ (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_view_certificate')) &&
+ (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_certificate_actions'))
+ ) {
+ wp_send_json_error(array('message' => 'Security check failed'));
+ }
+
+ // Get certificate by different methods
+ $certificate = null;
+
+ // Method 1: Direct certificate ID
+ if (isset($_POST['certificate_id']) && absint($_POST['certificate_id'])) {
+ $certificate_id = absint($_POST['certificate_id']);
+ $certificate = $this->certificate_manager->get_certificate($certificate_id);
+ }
+ // Method 2: Event ID and Attendee ID
+ elseif (isset($_POST['event_id']) && isset($_POST['attendee_id'])) {
+ $event_id = absint($_POST['event_id']);
+ $attendee_id = absint($_POST['attendee_id']);
+ $certificate = $this->certificate_manager->get_certificate_by_attendee($event_id, $attendee_id);
+ } else {
+ wp_send_json_error(array('message' => 'Missing certificate information'));
+ }
+
+ // Check if certificate exists
+ if (!$certificate) {
+ wp_send_json_error(array('message' => 'Certificate not found'));
+ }
+
+ // Shorthand for certificate ID
+ $certificate_id = $certificate->certificate_id;
+
+ // Check user permissions (must be the event author or admin)
+ $event = get_post($certificate->event_id);
+
+ if (!$event || !current_user_can('edit_post', $event->ID)) {
+ wp_send_json_error(array('message' => 'You do not have permission to view this certificate'));
+ }
+
+ // Get attendee name
+ $attendee_name = get_post_meta($certificate->attendee_id, '_tribe_tickets_full_name', true);
+ if (empty($attendee_name)) {
+ $attendee_name = 'Attendee #' . $certificate->attendee_id;
+ }
+
+ // Generate secure download URL
+ $certificate_data = array(
+ 'file_path' => $certificate->file_path,
+ 'event_name' => $event->post_title,
+ 'attendee_name' => $attendee_name
+ );
+
+ $download_url = $this->certificate_security->generate_download_token($certificate_id, $certificate_data);
+
+ if (!$download_url) {
+ wp_send_json_error(array('message' => 'Failed to generate download URL'));
+ }
+
+ wp_send_json_success(array('url' => $download_url));
+ }
+
+ /**
+ * AJAX handler for emailing a certificate.
+ */
+ public function email_certificate() {
+ // Verify nonce
+ if (
+ (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_email_certificate')) &&
+ (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_certificate_actions'))
+ ) {
+ wp_send_json_error(array('message' => 'Security check failed'));
+ }
+
+ // Get certificate by different methods
+ $certificate = null;
+
+ // Method 1: Direct certificate ID
+ if (isset($_POST['certificate_id']) && absint($_POST['certificate_id'])) {
+ $certificate_id = absint($_POST['certificate_id']);
+ $certificate = $this->certificate_manager->get_certificate($certificate_id);
+ }
+ // Method 2: Event ID and Attendee ID
+ elseif (isset($_POST['event_id']) && isset($_POST['attendee_id'])) {
+ $event_id = absint($_POST['event_id']);
+ $attendee_id = absint($_POST['attendee_id']);
+ $certificate = $this->certificate_manager->get_certificate_by_attendee($event_id, $attendee_id);
+ } else {
+ wp_send_json_error(array('message' => 'Missing certificate information'));
+ }
+
+ // Check if certificate exists
+ if (!$certificate) {
+ wp_send_json_error(array('message' => 'Certificate not found'));
+ }
+
+ // Shorthand for certificate ID
+ $certificate_id = $certificate->certificate_id;
+
+ // Check if certificate is revoked
+ if ($certificate->revoked) {
+ wp_send_json_error(array('message' => 'Cannot email a revoked certificate'));
+ }
+
+ // Check user permissions (must be the event author or admin)
+ $event = get_post($certificate->event_id);
+
+ if (!$event || !current_user_can('edit_post', $event->ID)) {
+ wp_send_json_error(array('message' => 'You do not have permission to email this certificate'));
+ }
+
+ // Get attendee email
+ $attendee_email = get_post_meta($certificate->attendee_id, '_tribe_tickets_email', true);
+
+ if (empty($attendee_email)) {
+ wp_send_json_error(array('message' => 'Attendee email not found'));
+ }
+
+ // Get attendee name
+ $attendee_name = get_post_meta($certificate->attendee_id, '_tribe_tickets_full_name', true);
+ if (empty($attendee_name)) {
+ $attendee_name = 'Attendee';
+ }
+
+ // Generate secure download URL (expires in 7 days)
+ $certificate_data = array(
+ 'file_path' => $certificate->file_path,
+ 'event_name' => $event->post_title,
+ 'attendee_name' => $attendee_name
+ );
+
+ $download_url = $this->certificate_security->generate_download_token($certificate_id, $certificate_data, 7 * DAY_IN_SECONDS);
+
+ if (!$download_url) {
+ wp_send_json_error(array('message' => 'Failed to generate download URL'));
+ }
+
+ // Get current user (sender) info
+ $sender_name = wp_get_current_user()->display_name;
+
+ // Email subject
+ $subject = sprintf(
+ __('Your Certificate for %s', 'hvac-community-events'),
+ $event->post_title
+ );
+
+ // Email body
+ $message = sprintf(
+ __("Hello %s,\n\nThank you for attending %s.\n\nYour certificate of completion is now available. Please click the link below to download your certificate:\n\n%s\n\nThis link will expire in 7 days.\n\nRegards,\n%s", 'hvac-community-events'),
+ $attendee_name,
+ $event->post_title,
+ $download_url,
+ $sender_name
+ );
+
+ // Send email
+ $headers = array('Content-Type: text/plain; charset=UTF-8');
+ $sent = wp_mail($attendee_email, $subject, $message, $headers);
+
+ if ($sent) {
+ // Record email sent
+ $this->certificate_manager->mark_certificate_emailed($certificate_id);
+
+ wp_send_json_success(array('message' => 'Certificate sent successfully'));
+ } else {
+ wp_send_json_error(array('message' => 'Failed to send email'));
+ }
+ }
+
+ /**
+ * AJAX handler for revoking a certificate.
+ */
+ public function revoke_certificate() {
+ // Verify nonce
+ if (
+ (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_revoke_certificate')) &&
+ (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_certificate_actions'))
+ ) {
+ wp_send_json_error(array('message' => 'Security check failed'));
+ }
+
+ // Get reason for revocation
+ $reason = isset($_POST['reason']) ? sanitize_text_field($_POST['reason']) : '';
+
+ // Get certificate by different methods
+ $certificate = null;
+
+ // Method 1: Direct certificate ID
+ if (isset($_POST['certificate_id']) && absint($_POST['certificate_id'])) {
+ $certificate_id = absint($_POST['certificate_id']);
+ $certificate = $this->certificate_manager->get_certificate($certificate_id);
+ }
+ // Method 2: Event ID and Attendee ID
+ elseif (isset($_POST['event_id']) && isset($_POST['attendee_id'])) {
+ $event_id = absint($_POST['event_id']);
+ $attendee_id = absint($_POST['attendee_id']);
+ $certificate = $this->certificate_manager->get_certificate_by_attendee($event_id, $attendee_id);
+ } else {
+ wp_send_json_error(array('message' => 'Missing certificate information'));
+ }
+
+ // Check if certificate exists
+ if (!$certificate) {
+ wp_send_json_error(array('message' => 'Certificate not found'));
+ }
+
+ // Shorthand for certificate ID
+ $certificate_id = $certificate->certificate_id;
+
+ // Check if certificate is already revoked
+ if ($certificate->revoked) {
+ wp_send_json_error(array('message' => 'Certificate is already revoked'));
+ }
+
+ // Check user permissions (must be the event author or admin)
+ $event = get_post($certificate->event_id);
+
+ if (!$event || !current_user_can('edit_post', $event->ID)) {
+ wp_send_json_error(array('message' => 'You do not have permission to revoke this certificate'));
+ }
+
+ // Revoke the certificate
+ $revoked = $this->certificate_manager->revoke_certificate(
+ $certificate_id,
+ get_current_user_id(),
+ $reason
+ );
+
+ if ($revoked) {
+ // Get updated certificate for revocation date
+ $updated_certificate = $this->certificate_manager->get_certificate($certificate_id);
+ $revoked_date = date_i18n(get_option('date_format'), strtotime($updated_certificate->revoked_date));
+
+ wp_send_json_success(array(
+ 'message' => 'Certificate revoked successfully',
+ 'revoked_date' => $revoked_date
+ ));
+ } else {
+ wp_send_json_error(array('message' => 'Failed to revoke certificate'));
+ }
+ }
+}
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-generator.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-generator.php
new file mode 100644
index 00000000..716d7044
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-generator.php
@@ -0,0 +1,551 @@
+certificate_manager = HVAC_Certificate_Manager::instance();
+ }
+
+ /**
+ * Generate a certificate for an attendee.
+ *
+ * @param int $event_id The event ID.
+ * @param int $attendee_id The attendee ID.
+ * @param array $custom_data Optional custom data to override defaults.
+ * @param int $generated_by The ID of the user who generated the certificate.
+ *
+ * @return int|false The certificate ID if successful, false otherwise.
+ */
+ public function generate_certificate($event_id, $attendee_id, $custom_data = array(), $generated_by = 0) {
+ // Check if certificate already exists
+ if ($this->certificate_manager->certificate_exists($event_id, $attendee_id)) {
+ HVAC_Logger::warning("Certificate already exists for event $event_id and attendee $attendee_id", 'Certificates');
+ return false;
+ }
+
+ // Get attendee data
+ $attendee_data = $this->get_attendee_data($attendee_id);
+
+ if (empty($attendee_data)) {
+ HVAC_Logger::error("Failed to retrieve attendee data for ID: $attendee_id", 'Certificates');
+ return false;
+ }
+
+ // Get event data
+ $event_data = $this->get_event_data($event_id);
+
+ if (empty($event_data)) {
+ HVAC_Logger::error("Failed to retrieve event data for ID: $event_id", 'Certificates');
+ return false;
+ }
+
+ // Merge custom data
+ $certificate_data = array_merge($attendee_data, $event_data, $custom_data);
+
+ // Create certificate record first
+ $user_id = $attendee_data['user_id'] ?? 0;
+ $certificate_id = $this->certificate_manager->create_certificate($event_id, $attendee_id, $user_id, '', $generated_by);
+
+ if (!$certificate_id) {
+ HVAC_Logger::error("Failed to create certificate record for event $event_id and attendee $attendee_id", 'Certificates');
+ return false;
+ }
+
+ // Generate PDF and get file path
+ $file_path = $this->generate_pdf($certificate_id, $certificate_data);
+
+ if (!$file_path) {
+ // Delete the certificate record if PDF generation failed
+ $this->certificate_manager->delete_certificate($certificate_id);
+
+ HVAC_Logger::error("Failed to generate PDF for certificate ID: $certificate_id", 'Certificates');
+ return false;
+ }
+
+ // Update certificate record with file path
+ $this->certificate_manager->update_certificate_file($certificate_id, $file_path);
+
+ return $certificate_id;
+ }
+
+ /**
+ * Generate a PDF certificate.
+ *
+ * @param int $certificate_id The certificate ID.
+ * @param array $certificate_data The certificate data.
+ *
+ * @return string|false The relative file path if successful, false otherwise.
+ */
+ protected function generate_pdf($certificate_id, $certificate_data) {
+ // Get certificate and verify it exists
+ $certificate = $this->certificate_manager->get_certificate($certificate_id);
+
+ if (!$certificate) {
+ return false;
+ }
+
+ // Create a custom TCPDF class extension (for header/footer)
+ $pdf = $this->create_certificate_pdf();
+
+ // Add a page
+ $pdf->AddPage('L', 'LETTER'); // Landscape, Letter size
+
+ // Set document metadata
+ $event_name = $certificate_data['event_name'] ?? 'HVAC Training';
+ $attendee_name = $certificate_data['attendee_name'] ?? 'Attendee';
+
+ $pdf->SetCreator('HVAC Community Events');
+ $pdf->SetAuthor('Upskill HVAC');
+ $pdf->SetTitle("Certificate of Completion - $event_name");
+ $pdf->SetSubject("Certificate for $attendee_name");
+ $pdf->SetKeywords("HVAC, Certificate, Training, $event_name");
+
+ // Render certificate content
+ $this->render_certificate_content($pdf, $certificate, $certificate_data);
+
+ // Get certificate storage path
+ $upload_dir = wp_upload_dir();
+ $cert_dir = $upload_dir['basedir'] . '/' . get_option('hvac_certificate_storage_path', 'hvac-certificates');
+
+ // Create directory if it doesn't exist
+ if (!file_exists($cert_dir)) {
+ wp_mkdir_p($cert_dir);
+ }
+
+ // Define file name and path
+ $file_name = sanitize_file_name(
+ 'certificate-' . $certificate->certificate_number . '-' .
+ sanitize_title($attendee_name) . '.pdf'
+ );
+
+ $event_dir = $cert_dir . '/' . $certificate->event_id;
+
+ // Create event directory if it doesn't exist
+ if (!file_exists($event_dir)) {
+ wp_mkdir_p($event_dir);
+ }
+
+ $full_path = $event_dir . '/' . $file_name;
+ $relative_path = get_option('hvac_certificate_storage_path', 'hvac-certificates') .
+ '/' . $certificate->event_id . '/' . $file_name;
+
+ // Save the PDF file
+ try {
+ $pdf->Output($full_path, 'F'); // F means save to file
+
+ if (file_exists($full_path)) {
+ return $relative_path;
+ }
+ } catch (Exception $e) {
+ HVAC_Logger::error("Failed to save PDF file: " . $e->getMessage(), 'Certificates');
+ }
+
+ return false;
+ }
+
+ /**
+ * Create a TCPDF instance for certificate generation.
+ *
+ * @return TCPDF The TCPDF instance.
+ */
+ protected function create_certificate_pdf() {
+ // Create new PDF document
+ $pdf = new TCPDF('L', 'mm', 'LETTER', true, 'UTF-8', false);
+
+ // Set document information
+ $pdf->SetTitle('Certificate of Completion');
+ $pdf->SetAuthor('Upskill HVAC');
+
+ // Set margins
+ $pdf->SetMargins(15, 15, 15);
+
+ // Remove default header/footer
+ $pdf->setPrintHeader(false);
+ $pdf->setPrintFooter(false);
+
+ // Set auto page breaks
+ $pdf->SetAutoPageBreak(false, 0);
+
+ // Set default font
+ $pdf->SetFont('helvetica', '', 10);
+
+ return $pdf;
+ }
+
+ /**
+ * Render certificate content on PDF.
+ *
+ * @param TCPDF $pdf The TCPDF instance.
+ * @param object $certificate The certificate object.
+ * @param array $certificate_data The certificate data.
+ */
+ protected function render_certificate_content($pdf, $certificate, $certificate_data) {
+ // Set background image if available
+ $this->add_certificate_background($pdf);
+
+ // Certificate title
+ $pdf->SetFont('helvetica', 'B', 30);
+ $pdf->SetTextColor(0, 77, 155); // Blue
+ $pdf->SetY(30);
+ $pdf->Cell(0, 20, 'CERTIFICATE OF COMPLETION', 0, 1, 'C');
+
+ // Description text
+ $pdf->SetFont('helvetica', '', 12);
+ $pdf->SetTextColor(77, 77, 77); // Dark gray
+ $pdf->SetY(55);
+ $pdf->Cell(0, 10, 'This certificate is awarded to', 0, 1, 'C');
+
+ // Attendee name
+ $attendee_name = $certificate_data['attendee_name'] ?? 'Attendee Name';
+ $pdf->SetFont('helvetica', 'B', 24);
+ $pdf->SetTextColor(0, 0, 0); // Black
+ $pdf->Cell(0, 15, $attendee_name, 0, 1, 'C');
+
+ // Course completion text
+ $pdf->SetFont('helvetica', '', 12);
+ $pdf->SetTextColor(77, 77, 77); // Dark gray
+ $pdf->Cell(0, 10, 'for successfully completing', 0, 1, 'C');
+
+ // Event name
+ $event_name = $certificate_data['event_name'] ?? 'HVAC Training Course';
+ $pdf->SetFont('helvetica', 'B', 18);
+ $pdf->SetTextColor(0, 77, 155); // Blue
+ $pdf->Cell(0, 15, $event_name, 0, 1, 'C');
+
+ // Event date
+ $event_date = $certificate_data['event_date_formatted'] ?? date('F j, Y');
+ $pdf->SetFont('helvetica', '', 12);
+ $pdf->SetTextColor(77, 77, 77); // Dark gray
+ $pdf->Cell(0, 10, 'on ' . $event_date, 0, 1, 'C');
+
+ // Draw a line
+ $pdf->SetDrawColor(0, 77, 155); // Blue
+ $pdf->SetLineWidth(0.5);
+ $pdf->Line(70, 150, 190, 150);
+
+ // Instructor name and signature
+ $instructor_name = $certificate_data['instructor_name'] ?? 'Instructor Name';
+
+ // Add instructor signature if available
+ if (!empty($certificate_data['instructor_signature'])) {
+ $signature_path = $certificate_data['instructor_signature'];
+ if (file_exists($signature_path)) {
+ $pdf->Image($signature_path, 110, 130, 40, 0, '', '', '', false, 300);
+ }
+ }
+
+ $pdf->SetY(155);
+ $pdf->SetFont('helvetica', 'B', 12);
+ $pdf->SetTextColor(0, 0, 0); // Black
+ $pdf->Cell(0, 10, $instructor_name, 0, 1, 'C');
+
+ $pdf->SetFont('helvetica', '', 10);
+ $pdf->SetTextColor(77, 77, 77); // Dark gray
+ $pdf->Cell(0, 10, 'Instructor', 0, 1, 'C');
+
+ // Add organization name
+ $organization_name = $certificate_data['organization_name'] ?? 'Upskill HVAC';
+ $pdf->SetY(175);
+ $pdf->SetFont('helvetica', 'B', 10);
+ $pdf->SetTextColor(0, 0, 0); // Black
+ $pdf->Cell(0, 10, $organization_name, 0, 1, 'C');
+
+ // Add venue info
+ $venue_name = $certificate_data['venue_name'] ?? '';
+ if (!empty($venue_name)) {
+ $pdf->SetFont('helvetica', '', 10);
+ $pdf->SetTextColor(77, 77, 77); // Dark gray
+ $pdf->Cell(0, 10, $venue_name, 0, 1, 'C');
+ }
+
+ // Add certificate details at the bottom
+ $pdf->SetFont('helvetica', '', 8);
+ $pdf->SetTextColor(128, 128, 128); // Light gray
+ $pdf->SetY(195);
+ $pdf->Cell(0, 10, 'Certificate #: ' . $certificate->certificate_number . ' | Issue Date: ' . date('F j, Y', strtotime($certificate->date_generated)), 0, 1, 'C');
+
+ // Add logo
+ $this->add_logo($pdf);
+ }
+
+ /**
+ * Add certificate background.
+ *
+ * @param TCPDF $pdf The TCPDF instance.
+ */
+ protected function add_certificate_background($pdf) {
+ // Check if custom background exists
+ $background_path = HVAC_CE_PLUGIN_DIR . 'assets/images/certificate-background.jpg';
+
+ if (file_exists($background_path)) {
+ // Add background
+ $pdf->Image($background_path, 0, 0, $pdf->getPageWidth(), $pdf->getPageHeight(), '', '', '', false, 300);
+ } else {
+ // Create a simple background with border
+ $pdf->SetFillColor(255, 255, 255);
+ $pdf->Rect(0, 0, $pdf->getPageWidth(), $pdf->getPageHeight(), 'F');
+
+ // Add border
+ $pdf->SetDrawColor(0, 77, 155); // Blue
+ $pdf->SetLineWidth(1.5);
+ $pdf->Rect(5, 5, $pdf->getPageWidth() - 10, $pdf->getPageHeight() - 10, 'D');
+
+ // Add inner border
+ $pdf->SetDrawColor(200, 200, 200); // Light gray
+ $pdf->SetLineWidth(0.5);
+ $pdf->Rect(10, 10, $pdf->getPageWidth() - 20, $pdf->getPageHeight() - 20, 'D');
+ }
+ }
+
+ /**
+ * Add logo to certificate.
+ *
+ * @param TCPDF $pdf The TCPDF instance.
+ */
+ protected function add_logo($pdf) {
+ // Check if logo exists
+ $logo_path = HVAC_CE_PLUGIN_DIR . 'assets/images/certificate-logo.png';
+
+ if (file_exists($logo_path)) {
+ // Add logo at top left
+ $pdf->Image($logo_path, 15, 15, 40, 0, '', '', '', false, 300);
+ }
+ }
+
+ /**
+ * Get attendee data from Event Tickets.
+ *
+ * @param int $attendee_id The attendee ID.
+ *
+ * @return array Attendee data.
+ */
+ protected function get_attendee_data($attendee_id) {
+ $attendee_data = array();
+
+ // Get attendee post
+ $attendee = get_post($attendee_id);
+
+ if (!$attendee) {
+ return $attendee_data;
+ }
+
+ // Get attendee meta for Event Tickets
+ $attendee_name = get_post_meta($attendee_id, '_tribe_tickets_full_name', true);
+ $attendee_email = get_post_meta($attendee_id, '_tribe_tickets_email', true);
+ $user_id = 0;
+
+ // Try to find user by email
+ if (!empty($attendee_email)) {
+ $user = get_user_by('email', $attendee_email);
+ if ($user) {
+ $user_id = $user->ID;
+ }
+ }
+
+ // If name is empty, try alternative meta keys
+ if (empty($attendee_name)) {
+ $attendee_name = get_post_meta($attendee_id, '_tribe_rsvp_full_name', true);
+
+ // If still empty, check for first and last name separately
+ if (empty($attendee_name)) {
+ $first_name = get_post_meta($attendee_id, '_tribe_tickets_first_name', true);
+ $last_name = get_post_meta($attendee_id, '_tribe_tickets_last_name', true);
+
+ if (!empty($first_name) || !empty($last_name)) {
+ $attendee_name = trim($first_name . ' ' . $last_name);
+ }
+ }
+ }
+
+ // Build attendee data
+ $attendee_data = array(
+ 'attendee_id' => $attendee_id,
+ 'attendee_name' => $attendee_name,
+ 'attendee_email' => $attendee_email,
+ 'user_id' => $user_id
+ );
+
+ return $attendee_data;
+ }
+
+ /**
+ * Get event data from The Events Calendar.
+ *
+ * @param int $event_id The event ID.
+ *
+ * @return array Event data.
+ */
+ protected function get_event_data($event_id) {
+ $event_data = array();
+
+ // Get event post
+ $event = get_post($event_id);
+
+ if (!$event) {
+ return $event_data;
+ }
+
+ // Get event details
+ $event_name = $event->post_title;
+ $event_date = tribe_get_start_date($event_id, false, 'F j, Y');
+
+ // Get venue details
+ $venue_id = tribe_get_venue_id($event_id);
+ $venue_name = tribe_get_venue($event_id);
+
+ // Get organizer details
+ $organizer_id = tribe_get_organizer_id($event_id);
+ $organizer_name = tribe_get_organizer($event_id);
+ $instructor_name = $organizer_name; // Default instructor is the organizer
+
+ // Build event data
+ $event_data = array(
+ 'event_id' => $event_id,
+ 'event_name' => $event_name,
+ 'event_date' => get_post_meta($event_id, '_EventStartDate', true),
+ 'event_date_formatted' => $event_date,
+ 'venue_id' => $venue_id,
+ 'venue_name' => $venue_name,
+ 'organizer_id' => $organizer_id,
+ 'organization_name' => $organizer_name,
+ 'instructor_name' => $instructor_name
+ );
+
+ return $event_data;
+ }
+
+ /**
+ * Generate certificates in batch.
+ *
+ * @param int $event_id The event ID.
+ * @param array $attendee_ids Array of attendee IDs.
+ * @param array $custom_data Optional custom data to override defaults.
+ * @param int $generated_by The ID of the user who generated the certificates.
+ * @param bool $checked_in_only Whether to generate certificates only for checked-in attendees.
+ *
+ * @return array Results with success and error counts.
+ */
+ public function generate_certificates_batch($event_id, $attendee_ids, $custom_data = array(), $generated_by = 0, $checked_in_only = false) {
+ $results = array(
+ 'success' => 0,
+ 'error' => 0,
+ 'duplicate' => 0,
+ 'not_checked_in' => 0,
+ 'certificate_ids' => array()
+ );
+
+ if (empty($attendee_ids) || !is_array($attendee_ids)) {
+ return $results;
+ }
+
+ foreach ($attendee_ids as $attendee_id) {
+ // Check if certificate already exists
+ if ($this->certificate_manager->certificate_exists($event_id, $attendee_id)) {
+ $results['duplicate']++;
+ continue;
+ }
+
+ // Check attendee check-in status if required
+ if ($checked_in_only) {
+ $is_checked_in = $this->is_attendee_checked_in($attendee_id);
+
+ if (!$is_checked_in) {
+ $results['not_checked_in']++;
+ continue;
+ }
+ }
+
+ $certificate_id = $this->generate_certificate($event_id, $attendee_id, $custom_data, $generated_by);
+
+ if ($certificate_id) {
+ $results['success']++;
+ $results['certificate_ids'][] = $certificate_id;
+ } else {
+ $results['error']++;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Check if an attendee is checked in.
+ *
+ * @param int $attendee_id The attendee ID.
+ *
+ * @return bool True if checked in, false otherwise.
+ */
+ protected function is_attendee_checked_in($attendee_id) {
+ // Get attendee check-in status from Event Tickets
+ $check_in = get_post_meta($attendee_id, '_tribe_rsvp_checkedin', true);
+
+ // For Event Tickets Plus we need to check a different meta key
+ if (empty($check_in)) {
+ $check_in = get_post_meta($attendee_id, '_tribe_tpp_checkedin', true);
+ }
+
+ // If still empty, check the more general meta key
+ if (empty($check_in)) {
+ $check_in = get_post_meta($attendee_id, '_tribe_checkedin', true);
+ }
+
+ return !empty($check_in) && $check_in == 1;
+ }
+}
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-installer.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-installer.php
new file mode 100644
index 00000000..0a02aeba
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-installer.php
@@ -0,0 +1,194 @@
+get_charset_collate();
+ $table_name = $wpdb->prefix . 'hvac_certificates';
+
+ // Create the certificates table
+ $sql = "CREATE TABLE $table_name (
+ certificate_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+ event_id BIGINT(20) UNSIGNED NOT NULL,
+ attendee_id BIGINT(20) UNSIGNED NOT NULL,
+ user_id BIGINT(20) UNSIGNED DEFAULT NULL,
+ certificate_number VARCHAR(50) NOT NULL,
+ file_path VARCHAR(255) NOT NULL,
+ date_generated DATETIME NOT NULL,
+ generated_by BIGINT(20) UNSIGNED NOT NULL,
+ revoked TINYINT(1) NOT NULL DEFAULT 0,
+ revoked_date DATETIME DEFAULT NULL,
+ revoked_by BIGINT(20) UNSIGNED DEFAULT NULL,
+ revoked_reason TEXT DEFAULT NULL,
+ email_sent TINYINT(1) NOT NULL DEFAULT 0,
+ email_sent_date DATETIME DEFAULT NULL,
+ PRIMARY KEY (certificate_id),
+ UNIQUE KEY event_attendee (event_id, attendee_id),
+ KEY event_id (event_id),
+ KEY attendee_id (attendee_id),
+ KEY user_id (user_id),
+ KEY certificate_number (certificate_number),
+ KEY revoked (revoked)
+ ) $charset_collate;";
+
+ dbDelta($sql);
+
+ // Set the version option
+ update_option('hvac_certificates_db_version', $this->db_version);
+
+ // Create certificate options
+ if (false === get_option('hvac_certificate_counter')) {
+ add_option('hvac_certificate_counter', 0);
+ }
+
+ if (false === get_option('hvac_certificate_prefix')) {
+ add_option('hvac_certificate_prefix', 'HVAC-');
+ }
+
+ if (false === get_option('hvac_certificate_storage_path')) {
+ // Default path is within wp-content/uploads/hvac-certificates
+ add_option('hvac_certificate_storage_path', 'hvac-certificates');
+ }
+
+ // Create the certificate storage directory
+ $this->create_certificates_directory();
+ }
+
+ /**
+ * Create certificates directory if it doesn't exist.
+ *
+ * @return bool True if directory exists or was created, false otherwise.
+ */
+ public function create_certificates_directory() {
+ $upload_dir = wp_upload_dir();
+ $cert_dir = $upload_dir['basedir'] . '/' . get_option('hvac_certificate_storage_path', 'hvac-certificates');
+
+ // Create directory if it doesn't exist
+ if (!file_exists($cert_dir)) {
+ wp_mkdir_p($cert_dir);
+ }
+
+ // Create .htaccess file to protect directory
+ if (file_exists($cert_dir) && !file_exists($cert_dir . '/.htaccess')) {
+ $htaccess_content = "# Disable directory browsing
+Options -Indexes
+
+# Deny access to php files
+
+Order Allow,Deny
+Deny from all
+
+
+# Allow PDF downloads only via WordPress
+
+Order Allow,Deny
+Deny from all
+
+
+# Restrict direct access
+
+RewriteEngine On
+RewriteCond %{HTTP_REFERER} !^" . get_site_url() . " [NC]
+RewriteRule \\.(pdf)$ - [NC,F,L]
+";
+
+ file_put_contents($cert_dir . '/.htaccess', $htaccess_content);
+ }
+
+ return file_exists($cert_dir);
+ }
+
+ /**
+ * Check if the certificate tables exist and are up to date.
+ *
+ * @return bool True if tables are up to date, false otherwise.
+ */
+ public function check_tables() {
+ global $wpdb;
+
+ $installed_version = get_option('hvac_certificates_db_version');
+ $table_name = $wpdb->prefix . 'hvac_certificates';
+
+ // Check if table exists
+ $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
+
+ // If table doesn't exist or version is different, create/update tables
+ if (!$table_exists || $installed_version !== $this->db_version) {
+ $this->create_tables();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Upgrade routine for database tables.
+ *
+ * @return void
+ */
+ public function maybe_upgrade() {
+ $installed_version = get_option('hvac_certificates_db_version');
+
+ // If installed version is different from current version, run upgrade
+ if ($installed_version !== $this->db_version) {
+ $this->create_tables();
+ }
+ }
+}
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-manager.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-manager.php
new file mode 100644
index 00000000..8f7eae03
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-manager.php
@@ -0,0 +1,759 @@
+check_tables();
+ }
+
+ /**
+ * Generate a unique certificate number.
+ *
+ * @return string The generated certificate number.
+ */
+ public function generate_certificate_number() {
+ $prefix = get_option('hvac_certificate_prefix', 'HVAC-');
+ $counter = intval(get_option('hvac_certificate_counter', 0));
+
+ // Increment counter
+ $counter++;
+ update_option('hvac_certificate_counter', $counter);
+
+ // Format: PREFIX-YEAR-SEQUENTIAL (e.g., HVAC-2023-00001)
+ $year = date('Y');
+ $formatted_counter = str_pad($counter, 5, '0', STR_PAD_LEFT);
+
+ return $prefix . $year . '-' . $formatted_counter;
+ }
+
+ /**
+ * Creates a new certificate record in the database.
+ *
+ * @param int $event_id The event ID.
+ * @param int $attendee_id The attendee ID.
+ * @param int $user_id The associated user ID (if available).
+ * @param string $file_path The path to the certificate file.
+ * @param int $generated_by The ID of the user who generated the certificate.
+ *
+ * @return int|false The certificate ID if successful, false otherwise.
+ */
+ public function create_certificate($event_id, $attendee_id, $user_id = 0, $file_path = '', $generated_by = 0) {
+ global $wpdb;
+
+ // Get current user if not specified
+ if (empty($generated_by)) {
+ $generated_by = get_current_user_id();
+ }
+
+ // Generate certificate number
+ $certificate_number = $this->generate_certificate_number();
+
+ // Current date/time
+ $date_generated = current_time('mysql');
+
+ // Insert certificate record
+ $result = $wpdb->insert(
+ $wpdb->prefix . 'hvac_certificates',
+ array(
+ 'event_id' => $event_id,
+ 'attendee_id' => $attendee_id,
+ 'user_id' => $user_id,
+ 'certificate_number' => $certificate_number,
+ 'file_path' => $file_path,
+ 'date_generated' => $date_generated,
+ 'generated_by' => $generated_by,
+ 'revoked' => 0,
+ 'email_sent' => 0
+ ),
+ array(
+ '%d', // event_id
+ '%d', // attendee_id
+ '%d', // user_id
+ '%s', // certificate_number
+ '%s', // file_path
+ '%s', // date_generated
+ '%d', // generated_by
+ '%d', // revoked
+ '%d' // email_sent
+ )
+ );
+
+ if ($result) {
+ return $wpdb->insert_id;
+ }
+
+ return false;
+ }
+
+ /**
+ * Update the file path for a certificate.
+ *
+ * @param int $certificate_id The certificate ID.
+ * @param string $file_path The path to the certificate file.
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ public function update_certificate_file($certificate_id, $file_path) {
+ global $wpdb;
+
+ $result = $wpdb->update(
+ $wpdb->prefix . 'hvac_certificates',
+ array(
+ 'file_path' => $file_path
+ ),
+ array(
+ 'certificate_id' => $certificate_id
+ ),
+ array('%s'),
+ array('%d')
+ );
+
+ return $result !== false;
+ }
+
+ /**
+ * Mark a certificate as sent via email.
+ *
+ * @param int $certificate_id The certificate ID.
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ public function mark_certificate_emailed($certificate_id) {
+ global $wpdb;
+
+ $result = $wpdb->update(
+ $wpdb->prefix . 'hvac_certificates',
+ array(
+ 'email_sent' => 1,
+ 'email_sent_date' => current_time('mysql')
+ ),
+ array(
+ 'certificate_id' => $certificate_id
+ ),
+ array('%d', '%s'),
+ array('%d')
+ );
+
+ return $result !== false;
+ }
+
+ /**
+ * Revoke a certificate.
+ *
+ * @param int $certificate_id The certificate ID.
+ * @param string $reason The reason for revocation.
+ * @param int $revoked_by The ID of the user who revoked the certificate.
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ public function revoke_certificate($certificate_id, $reason = '', $revoked_by = 0) {
+ global $wpdb;
+
+ // Get current user if not specified
+ if (empty($revoked_by)) {
+ $revoked_by = get_current_user_id();
+ }
+
+ $result = $wpdb->update(
+ $wpdb->prefix . 'hvac_certificates',
+ array(
+ 'revoked' => 1,
+ 'revoked_date' => current_time('mysql'),
+ 'revoked_by' => $revoked_by,
+ 'revoked_reason' => $reason
+ ),
+ array(
+ 'certificate_id' => $certificate_id
+ ),
+ array('%d', '%s', '%d', '%s'),
+ array('%d')
+ );
+
+ return $result !== false;
+ }
+
+ /**
+ * Get a certificate by ID.
+ *
+ * @param int $certificate_id The certificate ID.
+ *
+ * @return object|false The certificate object if found, false otherwise.
+ */
+ public function get_certificate($certificate_id) {
+ global $wpdb;
+
+ $query = $wpdb->prepare(
+ "SELECT * FROM {$wpdb->prefix}hvac_certificates WHERE certificate_id = %d",
+ $certificate_id
+ );
+
+ return $wpdb->get_row($query);
+ }
+
+ /**
+ * Get a certificate by event ID and attendee ID.
+ *
+ * @param int $event_id The event ID.
+ * @param int $attendee_id The attendee ID.
+ *
+ * @return object|false The certificate object if found, false otherwise.
+ */
+ public function get_certificate_by_attendee($event_id, $attendee_id) {
+ global $wpdb;
+
+ $query = $wpdb->prepare(
+ "SELECT * FROM {$wpdb->prefix}hvac_certificates WHERE event_id = %d AND attendee_id = %d",
+ $event_id, $attendee_id
+ );
+
+ return $wpdb->get_row($query);
+ }
+
+ /**
+ * Get all certificates for an event.
+ *
+ * @param int $event_id The event ID.
+ * @param bool $include_revoked Whether to include revoked certificates.
+ *
+ * @return array Array of certificate objects.
+ */
+ public function get_certificates_by_event($event_id, $include_revoked = false) {
+ global $wpdb;
+
+ $where = "WHERE event_id = %d";
+ $params = array($event_id);
+
+ if (!$include_revoked) {
+ $where .= " AND revoked = 0";
+ }
+
+ $query = $wpdb->prepare(
+ "SELECT * FROM {$wpdb->prefix}hvac_certificates $where ORDER BY date_generated DESC",
+ $params
+ );
+
+ return $wpdb->get_results($query);
+ }
+
+ /**
+ * Get certificates count by event.
+ *
+ * @param int $event_id The event ID.
+ *
+ * @return array Certificate counts (total, active, revoked).
+ */
+ public function get_certificates_count_by_event($event_id) {
+ global $wpdb;
+
+ $query = $wpdb->prepare(
+ "SELECT
+ COUNT(*) as total,
+ SUM(CASE WHEN revoked = 0 THEN 1 ELSE 0 END) as active,
+ SUM(CASE WHEN revoked = 1 THEN 1 ELSE 0 END) as revoked,
+ SUM(CASE WHEN email_sent = 1 THEN 1 ELSE 0 END) as emailed
+ FROM {$wpdb->prefix}hvac_certificates
+ WHERE event_id = %d",
+ $event_id
+ );
+
+ $result = $wpdb->get_row($query);
+
+ return array(
+ 'total' => intval($result->total),
+ 'active' => intval($result->active),
+ 'revoked' => intval($result->revoked),
+ 'emailed' => intval($result->emailed)
+ );
+ }
+
+ /**
+ * Get overall certificate statistics.
+ *
+ * @return array Certificate statistics.
+ */
+ public function get_certificate_stats() {
+ global $wpdb;
+
+ $query = "SELECT
+ COUNT(DISTINCT attendee_id) as total_trainees,
+ COUNT(DISTINCT event_id) as total_events_with_certificates,
+ COUNT(*) as total_certificates,
+ SUM(CASE WHEN revoked = 1 THEN 1 ELSE 0 END) as total_revoked,
+ SUM(CASE WHEN email_sent = 1 THEN 1 ELSE 0 END) as total_emailed
+ FROM {$wpdb->prefix}hvac_certificates";
+
+ $result = $wpdb->get_row($query);
+
+ // Calculate average certificates per attendee
+ $avg_per_attendee = 0;
+ if (!empty($result->total_trainees)) {
+ $avg_per_attendee = $result->total_certificates / $result->total_trainees;
+ }
+
+ return array(
+ 'total_trainees' => intval($result->total_trainees),
+ 'total_events' => intval($result->total_events_with_certificates),
+ 'total_certificates' => intval($result->total_certificates),
+ 'total_revoked' => intval($result->total_revoked),
+ 'total_emailed' => intval($result->total_emailed),
+ 'avg_per_attendee' => round($avg_per_attendee, 2)
+ );
+ }
+
+ /**
+ * Get all certificates for a specific attendee.
+ *
+ * @param int $attendee_id The attendee ID.
+ * @param bool $include_revoked Whether to include revoked certificates.
+ *
+ * @return array Array of certificate objects.
+ */
+ public function get_certificates_by_attendee($attendee_id, $include_revoked = false) {
+ global $wpdb;
+
+ $where = "WHERE attendee_id = %d";
+ $params = array($attendee_id);
+
+ if (!$include_revoked) {
+ $where .= " AND revoked = 0";
+ }
+
+ $query = $wpdb->prepare(
+ "SELECT * FROM {$wpdb->prefix}hvac_certificates $where ORDER BY date_generated DESC",
+ $params
+ );
+
+ return $wpdb->get_results($query);
+ }
+
+ /**
+ * Get certificates by user ID.
+ *
+ * @param int $user_id The user ID.
+ * @param bool $include_revoked Whether to include revoked certificates.
+ *
+ * @return array Array of certificate objects.
+ */
+ public function get_certificates_by_user($user_id, $include_revoked = false) {
+ global $wpdb;
+
+ $where = "WHERE user_id = %d";
+ $params = array($user_id);
+
+ if (!$include_revoked) {
+ $where .= " AND revoked = 0";
+ }
+
+ $query = $wpdb->prepare(
+ "SELECT * FROM {$wpdb->prefix}hvac_certificates $where ORDER BY date_generated DESC",
+ $params
+ );
+
+ return $wpdb->get_results($query);
+ }
+
+ /**
+ * Get all events that have certificates.
+ *
+ * @param int $user_id Optional user ID to filter events by author.
+ * @return array Array of event objects with certificate data.
+ */
+ public function get_events_with_certificates($user_id = 0) {
+ global $wpdb;
+
+ // Get events with certificates
+ $query = "SELECT
+ event_id,
+ COUNT(*) as total_certificates,
+ SUM(CASE WHEN revoked = 0 THEN 1 ELSE 0 END) as active_certificates,
+ SUM(CASE WHEN revoked = 1 THEN 1 ELSE 0 END) as revoked_certificates,
+ SUM(CASE WHEN email_sent = 1 THEN 1 ELSE 0 END) as emailed_certificates,
+ MAX(date_generated) as last_generated
+ FROM {$wpdb->prefix}hvac_certificates
+ GROUP BY event_id
+ ORDER BY last_generated DESC";
+
+ $certificate_data = $wpdb->get_results($query, OBJECT_K);
+
+ // Get event data
+ $event_ids = array_keys($certificate_data);
+
+ if (empty($event_ids)) {
+ return array();
+ }
+
+ // Build WP_Query args
+ $args = array(
+ 'post_type' => Tribe__Events__Main::POSTTYPE,
+ 'post__in' => $event_ids,
+ 'posts_per_page' => -1,
+ 'orderby' => 'post__in',
+ 'post_status' => 'publish'
+ );
+
+ // Filter by user if specified
+ if ($user_id > 0) {
+ $args['author'] = $user_id;
+ }
+
+ $events = get_posts($args);
+
+ return $events;
+ }
+
+ /**
+ * Get certificates for events created by a specific user.
+ *
+ * @param int $user_id The user ID.
+ * @param array $args Additional query args (limit, offset, etc.).
+ *
+ * @return array Array of certificate objects.
+ */
+ public function get_user_certificates($user_id, $args = array()) {
+ global $wpdb;
+
+ $defaults = array(
+ 'page' => 1,
+ 'per_page' => 20,
+ 'orderby' => 'date_generated',
+ 'order' => 'DESC',
+ 'event_id' => 0,
+ 'revoked' => null,
+ 'limit' => 0
+ );
+
+ $args = wp_parse_args($args, $defaults);
+
+ // Build WHERE clause
+ $where = array();
+ $where_values = array();
+
+ // Get event IDs authored by this user
+ $events_query = new WP_Query(array(
+ 'post_type' => Tribe__Events__Main::POSTTYPE,
+ 'author' => $user_id,
+ 'posts_per_page' => -1,
+ 'fields' => 'ids',
+ 'post_status' => 'publish'
+ ));
+
+ $event_ids = $events_query->posts;
+
+ if (empty($event_ids)) {
+ return array();
+ }
+
+ // Filter by event ID if specified
+ if (!empty($args['event_id'])) {
+ // Check if the specified event belongs to the user
+ if (in_array($args['event_id'], $event_ids)) {
+ $where[] = "event_id = %d";
+ $where_values[] = $args['event_id'];
+ } else {
+ // Event doesn't belong to this user
+ return array();
+ }
+ } else {
+ // Include all user's events
+ $event_ids_string = implode(',', array_map('intval', $event_ids));
+ $where[] = "event_id IN ($event_ids_string)";
+ }
+
+ // Filter by revocation status if specified
+ if (isset($args['revoked']) && $args['revoked'] !== null) {
+ $where[] = "revoked = %d";
+ $where_values[] = (int) $args['revoked'];
+ }
+
+ // Build WHERE clause
+ $where_clause = !empty($where) ? "WHERE " . implode(" AND ", $where) : "";
+
+ // Build ORDER BY clause
+ $order_by = sanitize_sql_orderby($args['orderby'] . ' ' . $args['order']);
+
+ // Build LIMIT clause
+ $limit_clause = '';
+ if ($args['limit'] > 0) {
+ $limit_clause = "LIMIT %d";
+ $where_values[] = $args['limit'];
+ } elseif ($args['per_page'] > 0) {
+ $offset = ($args['page'] - 1) * $args['per_page'];
+ $limit_clause = "LIMIT %d, %d";
+ $where_values[] = $offset;
+ $where_values[] = $args['per_page'];
+ }
+
+ // Build final query
+ $query = "SELECT * FROM {$wpdb->prefix}hvac_certificates $where_clause ORDER BY $order_by $limit_clause";
+
+ // Prepare the query if we have where values
+ if (!empty($where_values)) {
+ $query = $wpdb->prepare($query, $where_values);
+ }
+
+ return $wpdb->get_results($query);
+ }
+
+ /**
+ * Get the total count of certificates for a specific user.
+ *
+ * @param int $user_id The user ID.
+ * @param array $args Additional query args.
+ *
+ * @return int Total count of certificates.
+ */
+ public function get_user_certificate_count($user_id, $args = array()) {
+ global $wpdb;
+
+ // Get event IDs authored by this user
+ $events_query = new WP_Query(array(
+ 'post_type' => Tribe__Events__Main::POSTTYPE,
+ 'author' => $user_id,
+ 'posts_per_page' => -1,
+ 'fields' => 'ids',
+ 'post_status' => 'publish'
+ ));
+
+ $event_ids = $events_query->posts;
+
+ if (empty($event_ids)) {
+ return 0;
+ }
+
+ // Build WHERE clause
+ $where = array();
+ $where_values = array();
+
+ // Filter by event ID if specified
+ if (!empty($args['event_id'])) {
+ // Check if the specified event belongs to the user
+ if (in_array($args['event_id'], $event_ids)) {
+ $where[] = "event_id = %d";
+ $where_values[] = $args['event_id'];
+ } else {
+ // Event doesn't belong to this user
+ return 0;
+ }
+ } else {
+ // Include all user's events
+ $event_ids_string = implode(',', array_map('intval', $event_ids));
+ $where[] = "event_id IN ($event_ids_string)";
+ }
+
+ // Filter by revocation status if specified
+ if (isset($args['revoked']) && $args['revoked'] !== null) {
+ $where[] = "revoked = %d";
+ $where_values[] = (int) $args['revoked'];
+ }
+
+ // Build WHERE clause
+ $where_clause = !empty($where) ? "WHERE " . implode(" AND ", $where) : "";
+
+ // Build final query
+ $query = "SELECT COUNT(*) FROM {$wpdb->prefix}hvac_certificates $where_clause";
+
+ // Prepare the query if we have where values
+ if (!empty($where_values)) {
+ $query = $wpdb->prepare($query, $where_values);
+ }
+
+ return intval($wpdb->get_var($query));
+ }
+
+ /**
+ * Get certificate statistics for a specific user.
+ *
+ * @param int $user_id The user ID.
+ *
+ * @return array Certificate statistics.
+ */
+ public function get_user_certificate_stats($user_id) {
+ global $wpdb;
+
+ // Get event IDs authored by this user
+ $events_query = new WP_Query(array(
+ 'post_type' => Tribe__Events__Main::POSTTYPE,
+ 'author' => $user_id,
+ 'posts_per_page' => -1,
+ 'fields' => 'ids',
+ 'post_status' => 'publish'
+ ));
+
+ $event_ids = $events_query->posts;
+
+ if (empty($event_ids)) {
+ return array(
+ 'total' => 0,
+ 'active' => 0,
+ 'revoked' => 0,
+ 'emailed' => 0
+ );
+ }
+
+ // Create string of event IDs for query
+ $event_ids_string = implode(',', array_map('intval', $event_ids));
+
+ $query = "SELECT
+ COUNT(*) as total,
+ SUM(CASE WHEN revoked = 0 THEN 1 ELSE 0 END) as active,
+ SUM(CASE WHEN revoked = 1 THEN 1 ELSE 0 END) as revoked,
+ SUM(CASE WHEN email_sent = 1 THEN 1 ELSE 0 END) as emailed
+ FROM {$wpdb->prefix}hvac_certificates
+ WHERE event_id IN ($event_ids_string)";
+
+ $result = $wpdb->get_row($query);
+
+ return array(
+ 'total' => intval($result->total),
+ 'active' => intval($result->active),
+ 'revoked' => intval($result->revoked),
+ 'emailed' => intval($result->emailed)
+ );
+ }
+
+ /**
+ * Get certificate file path.
+ *
+ * @param int $certificate_id The certificate ID.
+ *
+ * @return string|false The file path if found, false otherwise.
+ */
+ public function get_certificate_file_path($certificate_id) {
+ $certificate = $this->get_certificate($certificate_id);
+
+ if (!$certificate) {
+ return false;
+ }
+
+ // Get uploads directory
+ $upload_dir = wp_upload_dir();
+ $base_dir = $upload_dir['basedir'];
+
+ // Construct full path
+ $full_path = $base_dir . '/' . $certificate->file_path;
+
+ if (file_exists($full_path)) {
+ return $full_path;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get certificate file URL.
+ *
+ * @param int $certificate_id The certificate ID.
+ *
+ * @return string|false The file URL if found, false otherwise.
+ */
+ public function get_certificate_url($certificate_id) {
+ // Create a secure URL with nonce for downloading
+ $url = add_query_arg(
+ array(
+ 'action' => 'hvac_download_certificate',
+ 'certificate_id' => $certificate_id,
+ 'nonce' => wp_create_nonce('download_certificate_' . $certificate_id)
+ ),
+ admin_url('admin-ajax.php')
+ );
+
+ return $url;
+ }
+
+ /**
+ * Check if an attendee already has a certificate for an event.
+ *
+ * @param int $event_id The event ID.
+ * @param int $attendee_id The attendee ID.
+ *
+ * @return bool True if a certificate exists, false otherwise.
+ */
+ public function certificate_exists($event_id, $attendee_id) {
+ $certificate = $this->get_certificate_by_attendee($event_id, $attendee_id);
+ return !empty($certificate);
+ }
+
+ /**
+ * Delete a certificate record and its file.
+ *
+ * @param int $certificate_id The certificate ID.
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ public function delete_certificate($certificate_id) {
+ global $wpdb;
+
+ // Get certificate to get file path
+ $certificate = $this->get_certificate($certificate_id);
+
+ if (!$certificate) {
+ return false;
+ }
+
+ // Delete file if it exists
+ if (!empty($certificate->file_path)) {
+ $upload_dir = wp_upload_dir();
+ $full_path = $upload_dir['basedir'] . '/' . $certificate->file_path;
+
+ if (file_exists($full_path)) {
+ unlink($full_path);
+ }
+ }
+
+ // Delete from database
+ $result = $wpdb->delete(
+ $wpdb->prefix . 'hvac_certificates',
+ array('certificate_id' => $certificate_id),
+ array('%d')
+ );
+
+ return $result !== false;
+ }
+}
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-security.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-security.php
new file mode 100644
index 00000000..9fa7c47a
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-security.php
@@ -0,0 +1,254 @@
+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;
+ }
+
+ /**
+ * 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 .= "\n";
+ $htaccess_content .= " Order Allow,Deny\n";
+ $htaccess_content .= " Deny from all\n";
+ $htaccess_content .= "\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 = "maybe_initialize_settings();
+ }
+
+ /**
+ * Initialize default certificate settings if they don't exist.
+ */
+ public function maybe_initialize_settings() {
+ // Certificate counter for unique numbers
+ if (false === get_option('hvac_certificate_counter')) {
+ add_option('hvac_certificate_counter', 0);
+ }
+
+ // Certificate number prefix
+ if (false === get_option('hvac_certificate_prefix')) {
+ add_option('hvac_certificate_prefix', 'HVAC-');
+ }
+
+ // Certificate storage path (relative to wp-content/uploads/)
+ if (false === get_option('hvac_certificate_storage_path')) {
+ add_option('hvac_certificate_storage_path', 'hvac-certificates');
+ }
+
+ // Certificate paper size
+ if (false === get_option('hvac_certificate_paper_size')) {
+ add_option('hvac_certificate_paper_size', 'LETTER'); // LETTER, A4, etc.
+ }
+
+ // Certificate orientation
+ if (false === get_option('hvac_certificate_orientation')) {
+ add_option('hvac_certificate_orientation', 'L'); // L for landscape, P for portrait
+ }
+
+ // Certificate background color
+ if (false === get_option('hvac_certificate_bg_color')) {
+ add_option('hvac_certificate_bg_color', '#ffffff');
+ }
+
+ // Certificate border color
+ if (false === get_option('hvac_certificate_border_color')) {
+ add_option('hvac_certificate_border_color', '#0074be');
+ }
+
+ // Certificate title text
+ if (false === get_option('hvac_certificate_title_text')) {
+ add_option('hvac_certificate_title_text', 'CERTIFICATE OF COMPLETION');
+ }
+
+ // Certificate title color
+ if (false === get_option('hvac_certificate_title_color')) {
+ add_option('hvac_certificate_title_color', '#0074be');
+ }
+
+ // Certificate body text color
+ if (false === get_option('hvac_certificate_text_color')) {
+ add_option('hvac_certificate_text_color', '#333333');
+ }
+
+ // Certificate completion text
+ if (false === get_option('hvac_certificate_completion_text')) {
+ add_option('hvac_certificate_completion_text', 'This certificate is awarded to {attendee_name} for successfully completing {event_name} on {event_date}.');
+ }
+ }
+
+ /**
+ * Get all certificate settings.
+ *
+ * @return array All certificate settings.
+ */
+ public function get_all_settings() {
+ return array(
+ 'counter' => get_option('hvac_certificate_counter', 0),
+ 'prefix' => get_option('hvac_certificate_prefix', 'HVAC-'),
+ 'storage_path' => get_option('hvac_certificate_storage_path', 'hvac-certificates'),
+ 'paper_size' => get_option('hvac_certificate_paper_size', 'LETTER'),
+ 'orientation' => get_option('hvac_certificate_orientation', 'L'),
+ 'bg_color' => get_option('hvac_certificate_bg_color', '#ffffff'),
+ 'border_color' => get_option('hvac_certificate_border_color', '#0074be'),
+ 'title_text' => get_option('hvac_certificate_title_text', 'CERTIFICATE OF COMPLETION'),
+ 'title_color' => get_option('hvac_certificate_title_color', '#0074be'),
+ 'text_color' => get_option('hvac_certificate_text_color', '#333333'),
+ 'completion_text' => get_option('hvac_certificate_completion_text', 'This certificate is awarded to {attendee_name} for successfully completing {event_name} on {event_date}.')
+ );
+ }
+
+ /**
+ * Update a certificate setting.
+ *
+ * @param string $setting The setting key.
+ * @param mixed $value The setting value.
+ *
+ * @return bool True if successful, false otherwise.
+ */
+ public function update_setting($setting, $value) {
+ $option_name = 'hvac_certificate_' . $setting;
+
+ return update_option($option_name, $value);
+ }
+
+ /**
+ * Get a certificate setting.
+ *
+ * @param string $setting The setting key.
+ * @param mixed $default Optional default value.
+ *
+ * @return mixed The setting value or default.
+ */
+ public function get_setting($setting, $default = '') {
+ $option_name = 'hvac_certificate_' . $setting;
+
+ return get_option($option_name, $default);
+ }
+
+ /**
+ * Get available certificate placeholders.
+ *
+ * @return array Placeholders and their descriptions.
+ */
+ public function get_placeholders() {
+ return array(
+ '{attendee_name}' => 'The full name of the attendee',
+ '{event_name}' => 'The name of the event',
+ '{event_date}' => 'The date when the event occurred',
+ '{organization_name}' => 'The name of the training organization',
+ '{instructor_name}' => 'The name of the instructor',
+ '{venue_name}' => 'The name of the venue',
+ '{certificate_number}' => 'The unique certificate number',
+ '{issue_date}' => 'The date when the certificate was issued'
+ );
+ }
+
+ /**
+ * Replace placeholders in text with actual values.
+ *
+ * @param string $text The text with placeholders.
+ * @param array $data The data to replace placeholders with.
+ *
+ * @return string The text with placeholders replaced.
+ */
+ public function replace_placeholders($text, $data) {
+ $placeholders = array_keys($this->get_placeholders());
+ $replacements = array();
+
+ foreach ($placeholders as $placeholder) {
+ $key = str_replace(array('{', '}'), '', $placeholder);
+ $replacements[] = isset($data[$key]) ? $data[$key] : '';
+ }
+
+ return str_replace($placeholders, $replacements, $text);
+ }
+}
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-template.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-template.php
new file mode 100644
index 00000000..29881665
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/certificates/class-certificate-template.php
@@ -0,0 +1,437 @@
+settings = HVAC_Certificate_Settings::instance();
+
+ // Initialize hooks
+ $this->init_hooks();
+ }
+
+ /**
+ * Initialize hooks.
+ */
+ protected function init_hooks() {
+ // Add AJAX handlers for template preview
+ add_action('wp_ajax_hvac_preview_certificate', array($this, 'ajax_preview_certificate'));
+
+ // Add action to register custom upload folder
+ add_filter('upload_dir', array($this, 'certificate_upload_dir'));
+ }
+
+ /**
+ * Modify the upload directory for certificate files.
+ *
+ * @param array $dirs Upload directory information.
+ *
+ * @return array Modified upload directory.
+ */
+ public function certificate_upload_dir($dirs) {
+ // Only modify for certificate uploads
+ if (isset($_POST['certificate_upload']) && $_POST['certificate_upload'] === 'true') {
+ $certificate_dir = $this->settings->get_setting('storage_path', 'hvac-certificates');
+
+ $dirs['subdir'] = '/' . $certificate_dir;
+ $dirs['path'] = $dirs['basedir'] . $dirs['subdir'];
+ $dirs['url'] = $dirs['baseurl'] . $dirs['subdir'];
+ }
+
+ return $dirs;
+ }
+
+ /**
+ * Get available certificate templates.
+ *
+ * @return array List of certificate templates.
+ */
+ public function get_templates() {
+ $templates = array(
+ 'default' => array(
+ 'name' => __('Default', 'hvac-community-events'),
+ 'description' => __('Standard certificate template with blue accents', 'hvac-community-events'),
+ 'background' => HVAC_CE_PLUGIN_URL . 'assets/images/certificate-background.jpg',
+ 'thumbnail' => HVAC_CE_PLUGIN_URL . 'assets/images/certificate-background-thumb.jpg',
+ ),
+ );
+
+ // Allow filtering of templates
+ return apply_filters('hvac_certificate_templates', $templates);
+ }
+
+ /**
+ * Get the current certificate template.
+ *
+ * @return array The current template settings.
+ */
+ public function get_current_template() {
+ $template_id = $this->settings->get_setting('template', 'default');
+ $templates = $this->get_templates();
+
+ if (isset($templates[$template_id])) {
+ return $templates[$template_id];
+ }
+
+ // Fallback to default
+ return $templates['default'];
+ }
+
+ /**
+ * Get the path to the certificate background image.
+ *
+ * @return string|false The path to the background image or false if not found.
+ */
+ public function get_background_path() {
+ // Check for custom uploaded background first
+ $custom_bg = $this->settings->get_setting('custom_background', '');
+
+ if (!empty($custom_bg)) {
+ $upload_dir = wp_upload_dir();
+ $file_path = $upload_dir['basedir'] . '/' . $custom_bg;
+
+ if (file_exists($file_path)) {
+ return $file_path;
+ }
+ }
+
+ // Fallback to default template background
+ $default_bg = HVAC_CE_PLUGIN_DIR . 'assets/images/certificate-background.jpg';
+
+ if (file_exists($default_bg)) {
+ return $default_bg;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the path to the certificate logo image.
+ *
+ * @return string|false The path to the logo image or false if not found.
+ */
+ public function get_logo_path() {
+ // Check for custom uploaded logo first
+ $custom_logo = $this->settings->get_setting('custom_logo', '');
+
+ if (!empty($custom_logo)) {
+ $upload_dir = wp_upload_dir();
+ $file_path = $upload_dir['basedir'] . '/' . $custom_logo;
+
+ if (file_exists($file_path)) {
+ return $file_path;
+ }
+ }
+
+ // Fallback to default logo
+ $default_logo = HVAC_CE_PLUGIN_DIR . 'assets/images/certificate-logo.png';
+
+ if (file_exists($default_logo)) {
+ return $default_logo;
+ }
+
+ return false;
+ }
+
+ /**
+ * Generate a preview certificate for the settings page.
+ *
+ * @return string Path to the preview certificate file.
+ */
+ public function generate_preview() {
+ // Load TCPDF if not already included
+ if (!class_exists('TCPDF')) {
+ require_once HVAC_CE_PLUGIN_DIR . 'vendor/tecnickcom/tcpdf/tcpdf.php';
+ }
+
+ // Create PDF document
+ $pdf = new TCPDF(
+ $this->settings->get_setting('orientation', 'L'),
+ 'mm',
+ $this->settings->get_setting('paper_size', 'LETTER'),
+ true,
+ 'UTF-8',
+ false
+ );
+
+ // Set document information
+ $pdf->SetCreator('HVAC Community Events');
+ $pdf->SetAuthor('Upskill HVAC');
+ $pdf->SetTitle('Certificate Preview');
+
+ // Set margins
+ $pdf->SetMargins(15, 15, 15);
+
+ // Remove default header/footer
+ $pdf->setPrintHeader(false);
+ $pdf->setPrintFooter(false);
+
+ // Set auto page breaks
+ $pdf->SetAutoPageBreak(false, 0);
+
+ // Add a page
+ $pdf->AddPage();
+
+ // Get background image if available
+ $bg_path = $this->get_background_path();
+
+ if ($bg_path) {
+ // Add background
+ $pdf->Image($bg_path, 0, 0, $pdf->getPageWidth(), $pdf->getPageHeight(), '', '', '', false, 300);
+ } else {
+ // Create a simple background with border
+ $this->render_default_background($pdf);
+ }
+
+ // Add logo if available
+ $logo_path = $this->get_logo_path();
+
+ if ($logo_path) {
+ $pdf->Image($logo_path, 15, 15, 40, 0, '', '', '', false, 300);
+ }
+
+ // Render sample content
+ $this->render_preview_content($pdf);
+
+ // Create upload directory if it doesn't exist
+ $upload_dir = wp_upload_dir();
+ $preview_dir = $upload_dir['basedir'] . '/hvac-certificate-previews';
+
+ if (!file_exists($preview_dir)) {
+ wp_mkdir_p($preview_dir);
+ }
+
+ // Create an htaccess file to prevent direct access
+ $htaccess_file = $preview_dir . '/.htaccess';
+ if (!file_exists($htaccess_file)) {
+ $htaccess_content = "# Prevent direct access to files\n";
+ $htaccess_content .= "\n";
+ $htaccess_content .= " Order Allow,Deny\n";
+ $htaccess_content .= " Deny from all\n";
+ $htaccess_content .= "";
+
+ @file_put_contents($htaccess_file, $htaccess_content);
+ }
+
+ // Define preview file path
+ $preview_file = 'certificate-preview-' . time() . '.pdf';
+ $preview_path = $preview_dir . '/' . $preview_file;
+
+ // Save PDF
+ $pdf->Output($preview_path, 'F');
+
+ // Return relative path to preview file
+ return 'hvac-certificate-previews/' . $preview_file;
+ }
+
+ /**
+ * Render the default background for a certificate.
+ *
+ * @param TCPDF $pdf The PDF object.
+ */
+ protected function render_default_background($pdf) {
+ // Get background color
+ $bg_color = $this->hex_to_rgb($this->settings->get_setting('bg_color', '#ffffff'));
+
+ // Fill background
+ $pdf->SetFillColor($bg_color[0], $bg_color[1], $bg_color[2]);
+ $pdf->Rect(0, 0, $pdf->getPageWidth(), $pdf->getPageHeight(), 'F');
+
+ // Add border
+ $border_color = $this->hex_to_rgb($this->settings->get_setting('border_color', '#0074be'));
+ $pdf->SetDrawColor($border_color[0], $border_color[1], $border_color[2]);
+ $pdf->SetLineWidth(1.5);
+ $pdf->Rect(5, 5, $pdf->getPageWidth() - 10, $pdf->getPageHeight() - 10, 'D');
+
+ // Add inner border
+ $pdf->SetDrawColor(200, 200, 200); // Light gray
+ $pdf->SetLineWidth(0.5);
+ $pdf->Rect(10, 10, $pdf->getPageWidth() - 20, $pdf->getPageHeight() - 20, 'D');
+ }
+
+ /**
+ * Render content for the preview certificate.
+ *
+ * @param TCPDF $pdf The PDF object.
+ */
+ protected function render_preview_content($pdf) {
+ // Get title color
+ $title_color = $this->hex_to_rgb($this->settings->get_setting('title_color', '#0074be'));
+
+ // Get text color
+ $text_color = $this->hex_to_rgb($this->settings->get_setting('text_color', '#333333'));
+
+ // Certificate title
+ $pdf->SetFont('helvetica', 'B', 30);
+ $pdf->SetTextColor($title_color[0], $title_color[1], $title_color[2]);
+ $pdf->SetY(30);
+ $pdf->Cell(0, 20, $this->settings->get_setting('title_text', 'CERTIFICATE OF COMPLETION'), 0, 1, 'C');
+
+ // Description text
+ $pdf->SetFont('helvetica', '', 12);
+ $pdf->SetTextColor($text_color[0], $text_color[1], $text_color[2]);
+ $pdf->SetY(55);
+ $pdf->Cell(0, 10, 'This certificate is awarded to', 0, 1, 'C');
+
+ // Attendee name
+ $pdf->SetFont('helvetica', 'B', 24);
+ $pdf->SetTextColor(0, 0, 0); // Black
+ $pdf->Cell(0, 15, 'John Smith', 0, 1, 'C');
+
+ // Course completion text
+ $pdf->SetFont('helvetica', '', 12);
+ $pdf->SetTextColor($text_color[0], $text_color[1], $text_color[2]);
+ $pdf->Cell(0, 10, 'for successfully completing', 0, 1, 'C');
+
+ // Event name
+ $pdf->SetFont('helvetica', 'B', 18);
+ $pdf->SetTextColor($title_color[0], $title_color[1], $title_color[2]);
+ $pdf->Cell(0, 15, 'Advanced HVAC Troubleshooting Workshop', 0, 1, 'C');
+
+ // Event date
+ $pdf->SetFont('helvetica', '', 12);
+ $pdf->SetTextColor($text_color[0], $text_color[1], $text_color[2]);
+ $pdf->Cell(0, 10, 'on June 15, 2025', 0, 1, 'C');
+
+ // Draw a line
+ $pdf->SetDrawColor($title_color[0], $title_color[1], $title_color[2]);
+ $pdf->SetLineWidth(0.5);
+ $pdf->Line(70, 150, 190, 150);
+
+ // Instructor name
+ $pdf->SetY(155);
+ $pdf->SetFont('helvetica', 'B', 12);
+ $pdf->SetTextColor(0, 0, 0); // Black
+ $pdf->Cell(0, 10, 'Sarah Johnson', 0, 1, 'C');
+
+ $pdf->SetFont('helvetica', '', 10);
+ $pdf->SetTextColor($text_color[0], $text_color[1], $text_color[2]);
+ $pdf->Cell(0, 10, 'Instructor', 0, 1, 'C');
+
+ // Add organization name
+ $pdf->SetY(175);
+ $pdf->SetFont('helvetica', 'B', 10);
+ $pdf->SetTextColor(0, 0, 0); // Black
+ $pdf->Cell(0, 10, 'Upskill HVAC', 0, 1, 'C');
+
+ // Add venue info
+ $pdf->SetFont('helvetica', '', 10);
+ $pdf->SetTextColor($text_color[0], $text_color[1], $text_color[2]);
+ $pdf->Cell(0, 10, 'Technical Training Center, Boston', 0, 1, 'C');
+
+ // Add certificate details at the bottom
+ $pdf->SetFont('helvetica', '', 8);
+ $pdf->SetTextColor(128, 128, 128); // Light gray
+ $pdf->SetY(195);
+ $pdf->Cell(0, 10, 'Certificate #: HVAC-12345 | Issue Date: June 16, 2025', 0, 1, 'C');
+ }
+
+ /**
+ * AJAX handler for certificate preview generation.
+ */
+ public function ajax_preview_certificate() {
+ // Verify nonce
+ if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_certificate_preview')) {
+ wp_send_json_error(array('message' => 'Security check failed'));
+ }
+
+ // Check user capabilities
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(array('message' => 'Insufficient permissions'));
+ }
+
+ // Update settings first
+ if (isset($_POST['settings']) && is_array($_POST['settings'])) {
+ foreach ($_POST['settings'] as $key => $value) {
+ $this->settings->update_setting($key, sanitize_text_field($value));
+ }
+ }
+
+ // Generate preview
+ $preview_path = $this->generate_preview();
+
+ // Get full URL to preview
+ $upload_dir = wp_upload_dir();
+ $preview_url = $upload_dir['baseurl'] . '/' . $preview_path;
+
+ wp_send_json_success(array(
+ 'preview_url' => $preview_url,
+ 'message' => 'Preview generated successfully'
+ ));
+ }
+
+ /**
+ * Convert hexadecimal color to RGB.
+ *
+ * @param string $hex The hexadecimal color code.
+ *
+ * @return array RGB values.
+ */
+ protected function hex_to_rgb($hex) {
+ // Remove # if present
+ $hex = ltrim($hex, '#');
+
+ if (strlen($hex) == 3) {
+ $r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
+ $g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
+ $b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
+ } else {
+ $r = hexdec(substr($hex, 0, 2));
+ $g = hexdec(substr($hex, 2, 2));
+ $b = hexdec(substr($hex, 4, 2));
+ }
+
+ return array($r, $g, $b);
+ }
+}
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-community-events.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-community-events.php
index 48743323..560e20dd 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-community-events.php
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-community-events.php
@@ -55,9 +55,16 @@ class HVAC_Community_Events {
'community/class-login-handler.php',
'community/class-event-handler.php',
'class-hvac-dashboard-data.php',
- 'class-event-form-handler.php', // Add our form handler
- 'class-event-author-fixer.php', // Fix event author assignment
- 'class-hvac-dashboard.php' // New dashboard handler
+ 'class-event-form-handler.php', // Add our form handler
+ 'class-event-author-fixer.php', // Fix event author assignment
+ 'class-hvac-dashboard.php', // New dashboard handler
+ 'certificates/class-certificate-installer.php', // Certificate database installer
+ 'certificates/class-certificate-manager.php', // Certificate management
+ 'certificates/class-certificate-generator.php', // Certificate generation
+ 'certificates/class-certificate-settings.php', // Certificate settings
+ 'certificates/class-certificate-template.php', // Certificate template
+ 'certificates/class-certificate-security.php', // Certificate security
+ 'certificates/class-certificate-ajax-handler.php' // Certificate AJAX handling
];
// Make sure Login_Handler is loaded first for shortcode registration
$login_handler_path = HVAC_CE_PLUGIN_DIR . 'includes/community/class-login-handler.php';
@@ -113,6 +120,9 @@ class HVAC_Community_Events {
// Add authentication check for email attendees page
add_action('template_redirect', array($this, 'check_email_attendees_auth'));
+
+ // Add authentication check for certificate pages
+ add_action('template_redirect', array($this, 'check_certificate_pages_auth'));
} // End init_hooks
/**
@@ -139,6 +149,18 @@ class HVAC_Community_Events {
}
}
+ /**
+ * Check authentication for certificate pages
+ */
+ public function check_certificate_pages_auth() {
+ // Check if we're on certificate-related pages
+ if ((is_page('certificate-reports') || is_page('generate-certificates')) && !is_user_logged_in()) {
+ // Redirect to login page
+ wp_redirect(home_url('/community-login/?redirect_to=' . urlencode($_SERVER['REQUEST_URI'])));
+ exit;
+ }
+ }
+
/**
* Plugin activation (Should be called statically or from the main plugin file context)
*/
@@ -179,6 +201,11 @@ class HVAC_Community_Events {
// Initialize shortcodes
$this->init_shortcodes();
+ // Initialize certificate AJAX handler
+ if (class_exists('HVAC_Certificate_AJAX_Handler')) {
+ HVAC_Certificate_AJAX_Handler::instance();
+ }
+
// Initialize event form handler
if (class_exists('HVAC_Community_Events\Event_Form_Handler')) {
new \HVAC_Community_Events\Event_Form_Handler();
@@ -253,6 +280,12 @@ class HVAC_Community_Events {
// Add email attendees shortcode
add_shortcode('hvac_email_attendees', array($this, 'render_email_attendees'));
+ // Add certificate reports shortcode
+ add_shortcode('hvac_certificate_reports', array($this, 'render_certificate_reports'));
+
+ // Add generate certificates shortcode
+ add_shortcode('hvac_generate_certificates', array($this, 'render_generate_certificates'));
+
// Remove the event form shortcode as we're using TEC's shortcode instead
// add_shortcode('hvac_event_form', array('HVAC_Community_Event_Handler', 'render_event_form'));
@@ -355,6 +388,63 @@ class HVAC_Community_Events {
return ob_get_clean();
}
+ /**
+ * Render certificate reports content
+ */
+ public function render_certificate_reports() {
+ // Check if user is logged in
+ if (!is_user_logged_in()) {
+ return 'Please log in to view certificate reports.
';
+ }
+
+ // Check if the current user has permission to view certificate reports
+ // For now, we'll check if they're a trainer or have edit_posts capability
+ if (!current_user_can('hvac_trainer') && !current_user_can('edit_posts')) {
+ return 'You do not have permission to view certificate reports.
';
+ }
+
+ // Include the certificate reports template
+ ob_start();
+ include HVAC_CE_PLUGIN_DIR . 'templates/certificates/template-certificate-reports.php';
+ return ob_get_clean();
+ }
+
+ /**
+ * Render generate certificates content
+ */
+ public function render_generate_certificates() {
+ // Check if user is logged in
+ if (!is_user_logged_in()) {
+ return 'Please log in to generate certificates.
';
+ }
+
+ // Get event ID from URL parameter if available
+ $event_id = isset($_GET['event_id']) ? absint($_GET['event_id']) : 0;
+
+ // Check if the event exists and user has permission to view it when event_id is provided
+ if ($event_id > 0) {
+ $event = get_post($event_id);
+ if (!$event || get_post_type($event) !== Tribe__Events__Main::POSTTYPE) {
+ return 'Event not found or invalid.
';
+ }
+
+ // Check if the current user has permission to view this event
+ if ($event->post_author != get_current_user_id() && !current_user_can('edit_posts')) {
+ return 'You do not have permission to generate certificates for this event.
';
+ }
+ } else {
+ // If no event ID is provided, check general permissions
+ if (!current_user_can('hvac_trainer') && !current_user_can('edit_posts')) {
+ return 'You do not have permission to generate certificates.
';
+ }
+ }
+
+ // Include the generate certificates template
+ ob_start();
+ include HVAC_CE_PLUGIN_DIR . 'templates/certificates/template-generate-certificates.php';
+ return ob_get_clean();
+ }
+
/**
* Include custom templates for plugin pages
*/
@@ -406,6 +496,22 @@ class HVAC_Community_Events {
return $custom_template;
}
}
+
+ // Check for certificate-reports page
+ if (is_page('certificate-reports')) {
+ $custom_template = HVAC_CE_PLUGIN_DIR . 'templates/certificates/template-certificate-reports.php';
+ if (file_exists($custom_template)) {
+ return $custom_template;
+ }
+ }
+
+ // Check for generate-certificates page
+ if (is_page('generate-certificates')) {
+ $custom_template = HVAC_CE_PLUGIN_DIR . 'templates/certificates/template-generate-certificates.php';
+ if (file_exists($custom_template)) {
+ return $custom_template;
+ }
+ }
// Check for edit-profile page
if (is_page('edit-profile')) {
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/community/class-event-summary-data.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/community/class-event-summary-data.php
index 874edc27..c390d94a 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/community/class-event-summary-data.php
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/community/class-event-summary-data.php
@@ -213,6 +213,13 @@ class HVAC_Event_Summary_Data {
$transactions = [];
+ // Load certificate manager if it exists
+ $certificate_manager = null;
+ if (class_exists('HVAC_Certificate_Manager')) {
+ require_once HVAC_CE_PLUGIN_DIR . 'includes/certificates/class-certificate-manager.php';
+ $certificate_manager = HVAC_Certificate_Manager::instance();
+ }
+
// Check if Event Tickets is active and the necessary class/method exists
if ( class_exists( 'Tribe__Tickets__Tickets_Handler' ) && method_exists( Tribe__Tickets__Tickets_Handler::instance(), 'get_attendees_by_id' ) ) {
$attendees = Tribe__Tickets__Tickets_Handler::instance()->get_attendees_by_id( $this->event_id );
@@ -234,6 +241,26 @@ class HVAC_Event_Summary_Data {
$purchaser_email = $attendee['purchaser_email'];
}
+ // Get price if available (might vary based on provider)
+ $price = 0;
+ if (isset($attendee['price']) && is_numeric($attendee['price'])) {
+ $price = (float) $attendee['price'];
+ } elseif (isset($attendee['price_paid']) && is_numeric($attendee['price_paid'])) {
+ $price = (float) $attendee['price_paid'];
+ }
+
+ // Check if a certificate exists for this attendee
+ $certificate_status = 'Not Generated';
+ if ($certificate_manager) {
+ $certificate = $certificate_manager->get_certificate_by_attendee($this->event_id, $attendee_id);
+ if ($certificate) {
+ if ($certificate->revoked) {
+ $certificate_status = 'Revoked';
+ } else {
+ $certificate_status = $certificate->email_sent ? 'Sent' : 'Generated';
+ }
+ }
+ }
$transactions[] = [
'attendee_id' => $attendee_id,
@@ -244,7 +271,8 @@ class HVAC_Event_Summary_Data {
'purchaser_email' => $purchaser_email,
'security_code' => isset( $attendee['security_code'] ) ? $attendee['security_code'] : null,
'checked_in' => isset( $attendee['check_in'] ) ? (bool) $attendee['check_in'] : false,
- // Add other relevant fields if needed, e.g., price, order date
+ 'price' => $price,
+ 'certificate_status' => $certificate_status,
];
}
}
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
new file mode 100644
index 00000000..4c915475
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/certificates/template-certificate-reports.php
@@ -0,0 +1,218 @@
+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();
+?>
+
+
+
+
+
+
+
+
+
Active Certificates
+
+
+
+
Revoked Certificates
+
+
+
+
+
+
+
+
Certificate Filters
+
+
+
+
+
+
+
+
+
+
+ | Certificate # |
+ Event |
+ Attendee |
+ Date Generated |
+ Status |
+ Actions |
+
+
+
+ event_id);
+ $event_title = $event ? $event->post_title : 'Unknown Event';
+
+ // Get attendee name
+ $attendee_name = get_post_meta($certificate->attendee_id, '_tribe_tickets_full_name', true);
+ if (empty($attendee_name)) {
+ $attendee_name = 'Attendee #' . $certificate->attendee_id;
+ }
+
+ // Generate status class
+ $status_class = $certificate->revoked ? 'status-revoked' : 'status-active';
+ $status_text = $certificate->revoked ? 'Revoked' : 'Active';
+ ?>
+
+ | certificate_number); ?> |
+ |
+ |
+ date_generated))); ?> |
+ |
+
+ revoked) : ?>
+ View
+ Email
+ Revoke
+
+ Revoked
+
+ |
+
+
+
+
+
+
+ 1) : ?>
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/certificates/template-generate-certificates.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/certificates/template-generate-certificates.php
new file mode 100644
index 00000000..138d6752
--- /dev/null
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/certificates/template-generate-certificates.php
@@ -0,0 +1,366 @@
+generate_certificates_batch(
+ $submitted_event_id,
+ $selected_attendees,
+ array(), // Custom data (none for now)
+ $current_user_id, // Generated by current user
+ $checked_in_only // Only for checked-in attendees if selected
+ );
+
+ // Set success message if at least one certificate was generated
+ if ($generation_results['success'] > 0) {
+ $message_parts = array(
+ sprintf('Successfully generated %d certificate(s).', $generation_results['success'])
+ );
+
+ if ($generation_results['duplicate'] > 0) {
+ $message_parts[] = sprintf('%d duplicate(s) skipped.', $generation_results['duplicate']);
+ }
+
+ if ($generation_results['not_checked_in'] > 0) {
+ $message_parts[] = sprintf('%d attendee(s) not checked in.', $generation_results['not_checked_in']);
+ }
+
+ if ($generation_results['error'] > 0) {
+ $message_parts[] = sprintf('%d error(s).', $generation_results['error']);
+ }
+
+ $success_message = implode(' ', $message_parts);
+ } elseif ($generation_results['duplicate'] > 0 && $generation_results['error'] === 0 && $generation_results['not_checked_in'] === 0) {
+ $success_message = sprintf(
+ 'No new certificates generated. %d certificate(s) already exist for the selected attendees.',
+ $generation_results['duplicate']
+ );
+ } elseif ($generation_results['not_checked_in'] > 0 && $checked_in_only) {
+ $success_message = sprintf(
+ 'No new certificates generated. %d selected attendee(s) have not been checked in.',
+ $generation_results['not_checked_in']
+ );
+ } else {
+ $errors[] = 'Failed to generate certificates. Please try again.';
+ }
+ }
+ }
+}
+
+// Get user's events for the event selection step
+$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 attendees for the selected event
+$attendees = array();
+if ($event_id > 0) {
+ // Get all attendees for the event
+ $attendees = tribe_tickets_get_attendees($event_id);
+}
+
+// Get header and footer
+get_header();
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Step 1: Select Event
+
+
+
You don't have any events. Create an event first.
+
+
+
+
+
+ 0) : ?>
+
+
+
Step 2: Select Attendees
+
+
+
This event has no attendees. Sell tickets or add attendees to your event first.
+
+
+
+
+
+
+
+
Certificate Management Tools
+
After generating certificates, you can:
+
+ - View all certificates on the Certificate Reports page
+ - Email certificates to attendees directly from the reports page
+ - Revoke certificates that were issued incorrectly
+ - Download certificates in PDF format for printing or distribution
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/event-summary/template-event-summary.php b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/event-summary/template-event-summary.php
index b5a9e459..874e5622 100644
--- a/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/event-summary/template-event-summary.php
+++ b/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/templates/event-summary/template-event-summary.php
@@ -135,6 +135,10 @@ get_header();
if ( current_user_can( 'edit_post', $event_id ) ) {
$email_url = add_query_arg( 'event_id', $event_id, home_url( '/email-attendees/' ) );
echo 'Email Attendees';
+
+ // Certificate generation link
+ $certificate_url = add_query_arg( 'event_id', $event_id, home_url( '/generate-certificates/' ) );
+ echo 'Generate Certificates';
}
?>
@@ -257,6 +261,7 @@ get_header();
Price |
Order ID |
Checked In |
+ Certificate |
@@ -284,6 +289,31 @@ get_header();
|
+
+ $event_id,
+ 'attendee_id' => $txn['attendee_id']
+ ),
+ home_url('/generate-certificates/')
+ );
+ echo ' Generate';
+ } elseif ($certificate_status == 'Generated' || $certificate_status == 'Sent') {
+ // If certificate exists and is active, show view/email actions
+ echo ' View';
+ echo ' Email';
+ echo ' Revoke';
+ }
+ ?>
+ |