Implement certificate preview system with AJAX to eliminate page reloads and provide real-time certificate previews with secure token-based access as requested by user feedback.
🎯 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
add4911210
commit
f5cb8c07cf
7 changed files with 1068 additions and 114 deletions
255
wordpress-dev/tests/e2e/certificate-preview-simple.test.ts
Normal file
255
wordpress-dev/tests/e2e/certificate-preview-simple.test.ts
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { STAGING_URL, PATHS } from './config/staging-config';
|
||||
|
||||
/**
|
||||
* Simple Certificate Preview Test
|
||||
*
|
||||
* Tests the certificate preview functionality by checking:
|
||||
* - Generate Certificates page loads correctly with AJAX
|
||||
* - Certificate Reports page shows existing certificates
|
||||
* - Certificate security URL patterns are correct
|
||||
*/
|
||||
test.describe('Certificate Preview - Simple Tests', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login as test_trainer
|
||||
await page.goto(PATHS.login);
|
||||
await page.fill('#user_login', 'test_trainer');
|
||||
await page.fill('#user_pass', 'Test123!');
|
||||
await page.click('#wp-submit');
|
||||
|
||||
// Wait for dashboard redirect
|
||||
await page.waitForURL('**/hvac-dashboard/**');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('should access Generate Certificates page and verify AJAX functionality', async ({ page }) => {
|
||||
console.log('=== Testing Generate Certificates Page Access ===');
|
||||
|
||||
// Navigate to Generate Certificates
|
||||
await page.goto(PATHS.generateCertificates);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify page loads
|
||||
await expect(page.locator('h1')).toContainText('Generate Certificates');
|
||||
console.log('✅ Generate Certificates page loads correctly');
|
||||
|
||||
// Check if event selector exists (AJAX component)
|
||||
const eventSelect = page.locator('#event_id');
|
||||
await expect(eventSelect).toBeVisible();
|
||||
console.log('✅ Event selector is visible');
|
||||
|
||||
// Check if AJAX container exists (should be hidden initially)
|
||||
const attendeesContainer = page.locator('#step-select-attendees');
|
||||
await expect(attendeesContainer).toBeHidden();
|
||||
console.log('✅ AJAX attendees container exists and is hidden initially (correct behavior)');
|
||||
|
||||
// Verify AJAX JavaScript is loaded
|
||||
const ajaxScript = await page.evaluate(() => {
|
||||
return typeof window.hvacCertificateData !== 'undefined';
|
||||
});
|
||||
expect(ajaxScript).toBeTruthy();
|
||||
console.log('✅ Certificate AJAX JavaScript is loaded');
|
||||
|
||||
// Test event selection if events are available
|
||||
const options = await eventSelect.locator('option:not([value=""])').count();
|
||||
console.log(`Found ${options} events with attendees`);
|
||||
|
||||
if (options > 0) {
|
||||
// Select first event to test AJAX loading
|
||||
const firstOption = eventSelect.locator('option:not([value=""])').first();
|
||||
const eventValue = await firstOption.getAttribute('value');
|
||||
|
||||
console.log(`Testing AJAX with event ID: ${eventValue}`);
|
||||
await eventSelect.selectOption(eventValue);
|
||||
|
||||
// Wait a moment for AJAX to potentially load
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Check if attendees section becomes visible or gets content
|
||||
const attendeesVisible = await attendeesContainer.isVisible();
|
||||
console.log(`Attendees section visible after selection: ${attendeesVisible}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('should access Certificate Reports page and verify certificate security URLs', async ({ page }) => {
|
||||
console.log('=== Testing Certificate Reports Page ===');
|
||||
|
||||
// Navigate to Certificate Reports
|
||||
await page.goto(PATHS.certificatesReport);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify page loads
|
||||
await expect(page.locator('h1')).toContainText('Certificate Reports');
|
||||
console.log('✅ Certificate Reports page loads correctly');
|
||||
|
||||
// Check for certificate table or stats
|
||||
const certificateTable = page.locator('.hvac-certificate-table');
|
||||
const certificateStats = page.locator('.hvac-certificate-stats');
|
||||
|
||||
const hasTable = await certificateTable.isVisible();
|
||||
const hasStats = await certificateStats.isVisible();
|
||||
|
||||
console.log(`Certificate table visible: ${hasTable}`);
|
||||
console.log(`Certificate stats visible: ${hasStats}`);
|
||||
|
||||
if (hasTable) {
|
||||
// Check for certificate download links with security pattern
|
||||
const downloadLinks = page.locator('a[href*="hvac-certificate/"]');
|
||||
const linkCount = await downloadLinks.count();
|
||||
|
||||
console.log(`Found ${linkCount} certificate download links`);
|
||||
|
||||
if (linkCount > 0) {
|
||||
const firstLink = downloadLinks.first();
|
||||
const href = await firstLink.getAttribute('href');
|
||||
console.log(`Certificate download URL: ${href}`);
|
||||
|
||||
// Verify URL format matches security pattern
|
||||
expect(href).toMatch(/\/hvac-certificate\/[a-zA-Z0-9]{32}$/);
|
||||
console.log('✅ Certificate security URL format is correct');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasStats) {
|
||||
// Check certificate statistics
|
||||
const statCards = page.locator('.hvac-stat-card');
|
||||
const statCount = await statCards.count();
|
||||
console.log(`Found ${statCount} certificate statistics cards`);
|
||||
|
||||
if (statCount > 0) {
|
||||
for (let i = 0; i < statCount; i++) {
|
||||
const card = statCards.nth(i);
|
||||
const title = await card.locator('h3').textContent();
|
||||
const value = await card.locator('.hvac-stat-value').textContent();
|
||||
console.log(`Stat: ${title} = ${value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should verify certificate security system is active', async ({ page }) => {
|
||||
console.log('=== Testing Certificate Security System ===');
|
||||
|
||||
// Test direct access to certificate URL with invalid token
|
||||
const invalidToken = 'a'.repeat(32);
|
||||
const invalidUrl = `${STAGING_URL}/hvac-certificate/${invalidToken}`;
|
||||
|
||||
console.log(`Testing invalid certificate URL: ${invalidUrl}`);
|
||||
|
||||
const response = await page.goto(invalidUrl);
|
||||
|
||||
if (response) {
|
||||
const status = response.status();
|
||||
console.log(`Response status: ${status}`);
|
||||
|
||||
// Should get an error response for invalid token
|
||||
if (status === 200) {
|
||||
// Check for error message in content
|
||||
const content = await page.textContent('body');
|
||||
const hasError = /invalid|expired|not found/i.test(content);
|
||||
expect(hasError).toBeTruthy();
|
||||
console.log('✅ Invalid certificate URL shows error message');
|
||||
} else {
|
||||
// Non-200 status is also acceptable (403, 404, etc.)
|
||||
expect([403, 404]).toContain(status);
|
||||
console.log('✅ Invalid certificate URL returns error status');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should verify AJAX endpoints are accessible', async ({ page }) => {
|
||||
console.log('=== Testing Certificate AJAX Endpoints ===');
|
||||
|
||||
// Navigate to a page that loads certificate JavaScript
|
||||
await page.goto(PATHS.generateCertificates);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Test if AJAX endpoints are properly configured
|
||||
const ajaxData = await page.evaluate(() => {
|
||||
if (typeof window.hvacCertificateData !== 'undefined') {
|
||||
return {
|
||||
ajaxUrl: window.hvacCertificateData.ajaxUrl,
|
||||
hasGenerateNonce: !!window.hvacCertificateData.generateNonce,
|
||||
hasEmailNonce: !!window.hvacCertificateData.emailNonce,
|
||||
hasRevokeNonce: !!window.hvacCertificateData.revokeNonce
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
expect(ajaxData).not.toBeNull();
|
||||
expect(ajaxData.ajaxUrl).toContain('admin-ajax.php');
|
||||
expect(ajaxData.hasGenerateNonce).toBeTruthy();
|
||||
|
||||
console.log('✅ AJAX configuration is properly loaded');
|
||||
console.log(`AJAX URL: ${ajaxData.ajaxUrl}`);
|
||||
console.log(`Generate nonce present: ${ajaxData.hasGenerateNonce}`);
|
||||
console.log(`Email nonce present: ${ajaxData.hasEmailNonce}`);
|
||||
console.log(`Revoke nonce present: ${ajaxData.hasRevokeNonce}`);
|
||||
});
|
||||
|
||||
test('should verify certificate preview modal HTML structure exists', async ({ page }) => {
|
||||
console.log('=== Testing Certificate Preview Modal Structure ===');
|
||||
|
||||
// Navigate to Generate Certificates page
|
||||
await page.goto(PATHS.generateCertificates);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check if the preview JavaScript is loaded
|
||||
const previewFunctionExists = await page.evaluate(() => {
|
||||
return typeof window.GenerateCertificates !== 'undefined' &&
|
||||
typeof window.GenerateCertificates?.showCertificatePreview === 'function';
|
||||
});
|
||||
|
||||
if (previewFunctionExists) {
|
||||
console.log('✅ Certificate preview JavaScript functions are loaded');
|
||||
} else {
|
||||
console.log('ℹ️ Certificate preview functions not found in global scope');
|
||||
}
|
||||
|
||||
// Test creating a preview modal programmatically
|
||||
await page.evaluate(() => {
|
||||
// Simulate the preview button functionality
|
||||
if (typeof jQuery !== 'undefined') {
|
||||
const $ = jQuery;
|
||||
|
||||
// Create a test preview button
|
||||
$('body').append(`
|
||||
<button class="hvac-preview-certificate test-preview-btn"
|
||||
data-url="${window.location.origin}/hvac-certificate/test123456789012345678901234567890"
|
||||
data-attendee="Test Attendee">
|
||||
Test Preview
|
||||
</button>
|
||||
`);
|
||||
|
||||
// Trigger preview modal creation
|
||||
$('.test-preview-btn').click();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for modal to be created
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check if modal was created
|
||||
const modal = page.locator('#hvac-certificate-preview-modal');
|
||||
const modalExists = await modal.count() > 0;
|
||||
|
||||
if (modalExists) {
|
||||
console.log('✅ Certificate preview modal can be created');
|
||||
|
||||
// Verify modal structure
|
||||
const modalHeader = page.locator('#hvac-certificate-preview-modal .hvac-modal-header');
|
||||
const modalBody = page.locator('#hvac-certificate-preview-modal .hvac-modal-body');
|
||||
const iframe = page.locator('#hvac-certificate-preview-iframe');
|
||||
|
||||
expect(await modalHeader.count()).toBeGreaterThan(0);
|
||||
expect(await modalBody.count()).toBeGreaterThan(0);
|
||||
expect(await iframe.count()).toBeGreaterThan(0);
|
||||
|
||||
console.log('✅ Modal has correct structure with header, body, and iframe');
|
||||
} else {
|
||||
console.log('ℹ️ Preview modal creation needs further investigation');
|
||||
}
|
||||
});
|
||||
});
|
||||
222
wordpress-dev/tests/e2e/certificate-preview-test.test.ts
Normal file
222
wordpress-dev/tests/e2e/certificate-preview-test.test.ts
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { STAGING_URL, PATHS } from './config/staging-config';
|
||||
|
||||
/**
|
||||
* Certificate Preview Test
|
||||
*
|
||||
* Tests the new certificate preview functionality:
|
||||
* - AJAX certificate generation
|
||||
* - Preview URL generation with secure tokens
|
||||
* - Modal display of certificate content
|
||||
* - Real PDF preview (not blank iframe)
|
||||
*/
|
||||
test.describe('Certificate Preview Functionality', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login as test_trainer
|
||||
await page.goto(PATHS.login);
|
||||
await page.fill('#user_login', 'test_trainer');
|
||||
await page.fill('#user_pass', 'Test123!');
|
||||
await page.click('#wp-submit');
|
||||
|
||||
// Wait for dashboard redirect
|
||||
await page.waitForURL('**/hvac-dashboard/**');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('should generate certificates with preview functionality', async ({ page }) => {
|
||||
console.log('=== Testing Certificate Preview Functionality ===');
|
||||
|
||||
// Navigate to Generate Certificates
|
||||
await page.goto(PATHS.generateCertificates);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify page loads
|
||||
await expect(page.locator('h1')).toContainText('Generate Certificates');
|
||||
|
||||
// Select an event that has attendees
|
||||
const eventSelect = page.locator('#event_id');
|
||||
await expect(eventSelect).toBeVisible();
|
||||
|
||||
// Get available events
|
||||
const options = await eventSelect.locator('option:not([value=""])').all();
|
||||
console.log(`Found ${options.length} events with attendees`);
|
||||
|
||||
if (options.length === 0) {
|
||||
console.log('No events with attendees found - skipping test');
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the first event
|
||||
const firstOption = options[0];
|
||||
const eventValue = await firstOption.getAttribute('value');
|
||||
const eventText = await firstOption.textContent();
|
||||
console.log(`Selecting event: ${eventText} (ID: ${eventValue})`);
|
||||
|
||||
await eventSelect.selectOption(eventValue);
|
||||
|
||||
// Wait for attendees to load via AJAX
|
||||
await page.waitForSelector('#step-select-attendees', { state: 'visible' });
|
||||
await page.waitForSelector('.hvac-attendees-table', { state: 'visible' });
|
||||
|
||||
// Verify attendees loaded
|
||||
const attendeeRows = page.locator('.hvac-attendees-table tbody tr');
|
||||
const attendeeCount = await attendeeRows.count();
|
||||
console.log(`Found ${attendeeCount} attendees for this event`);
|
||||
|
||||
expect(attendeeCount).toBeGreaterThan(0);
|
||||
|
||||
// Select the first attendee without an existing certificate
|
||||
const firstCheckbox = page.locator('.attendee-checkbox').first();
|
||||
await expect(firstCheckbox).toBeVisible();
|
||||
await firstCheckbox.check();
|
||||
|
||||
// Generate certificates
|
||||
console.log('Generating certificates...');
|
||||
const generateButton = page.locator('#generate-certificates-form button[type="submit"]');
|
||||
await expect(generateButton).toBeVisible();
|
||||
await generateButton.click();
|
||||
|
||||
// Wait for AJAX response
|
||||
await page.waitForSelector('.hvac-success-message, .hvac-errors', { timeout: 10000 });
|
||||
|
||||
// Check for success message
|
||||
const successMessage = page.locator('.hvac-success-message');
|
||||
if (await successMessage.isVisible()) {
|
||||
const messageText = await successMessage.textContent();
|
||||
console.log('Certificate generation success:', messageText);
|
||||
|
||||
// Look for preview buttons
|
||||
const previewButtons = page.locator('.hvac-preview-certificate');
|
||||
const previewCount = await previewButtons.count();
|
||||
console.log(`Found ${previewCount} preview buttons`);
|
||||
|
||||
if (previewCount > 0) {
|
||||
console.log('Testing certificate preview modal...');
|
||||
|
||||
// Click the first preview button
|
||||
const firstPreviewButton = previewButtons.first();
|
||||
const attendeeName = await firstPreviewButton.getAttribute('data-attendee');
|
||||
const previewUrl = await firstPreviewButton.getAttribute('data-url');
|
||||
|
||||
console.log(`Preview button for: ${attendeeName}`);
|
||||
console.log(`Preview URL: ${previewUrl}`);
|
||||
|
||||
expect(previewUrl).toContain('hvac-certificate/');
|
||||
expect(previewUrl).toMatch(/hvac-certificate\/[a-zA-Z0-9]{32}$/);
|
||||
|
||||
// Click preview button
|
||||
await firstPreviewButton.click();
|
||||
|
||||
// Wait for modal to appear
|
||||
await page.waitForSelector('#hvac-certificate-preview-modal', { state: 'visible' });
|
||||
|
||||
// Verify modal structure
|
||||
const modal = page.locator('#hvac-certificate-preview-modal');
|
||||
await expect(modal).toBeVisible();
|
||||
|
||||
const modalTitle = page.locator('#hvac-certificate-preview-modal h3');
|
||||
await expect(modalTitle).toContainText(attendeeName);
|
||||
|
||||
// Verify iframe is present and has the correct src
|
||||
const iframe = page.locator('#hvac-certificate-preview-iframe');
|
||||
await expect(iframe).toBeVisible();
|
||||
|
||||
const iframeSrc = await iframe.getAttribute('src');
|
||||
console.log(`Iframe src: ${iframeSrc}`);
|
||||
expect(iframeSrc).toBe(previewUrl);
|
||||
|
||||
// Test iframe content loads (not blank)
|
||||
await page.waitForTimeout(3000); // Give iframe time to load
|
||||
|
||||
// Try to access iframe content to verify it's not blank
|
||||
// Note: Cross-origin restrictions may prevent full content verification
|
||||
try {
|
||||
await iframe.waitForLoadState('networkidle', { timeout: 5000 });
|
||||
console.log('Certificate preview iframe loaded successfully');
|
||||
} catch (error) {
|
||||
console.log('Iframe load check skipped due to cross-origin restrictions');
|
||||
}
|
||||
|
||||
// Test modal close functionality
|
||||
const closeButton = page.locator('.hvac-modal-close');
|
||||
await expect(closeButton).toBeVisible();
|
||||
await closeButton.click();
|
||||
|
||||
// Verify modal closes
|
||||
await page.waitForSelector('#hvac-certificate-preview-modal', { state: 'hidden' });
|
||||
console.log('Modal closed successfully');
|
||||
|
||||
console.log('✅ Certificate preview functionality working correctly');
|
||||
} else {
|
||||
console.log('⚠️ No preview buttons found - check if certificates were generated');
|
||||
}
|
||||
} else {
|
||||
const errorMessage = page.locator('.hvac-errors');
|
||||
if (await errorMessage.isVisible()) {
|
||||
const errorText = await errorMessage.textContent();
|
||||
console.log('Certificate generation error:', errorText);
|
||||
throw new Error(`Certificate generation failed: ${errorText}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should verify certificate security URL format', async ({ page }) => {
|
||||
console.log('=== Testing Certificate Security URL Format ===');
|
||||
|
||||
// Navigate to Certificate Reports to check existing certificates
|
||||
await page.goto(PATHS.certificatesReport);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check if there are any certificates
|
||||
const certificateTable = page.locator('.hvac-certificate-table');
|
||||
|
||||
if (await certificateTable.isVisible()) {
|
||||
const downloadLinks = page.locator('a[href*="hvac-certificate/"]');
|
||||
const linkCount = await downloadLinks.count();
|
||||
|
||||
if (linkCount > 0) {
|
||||
const firstLink = downloadLinks.first();
|
||||
const href = await firstLink.getAttribute('href');
|
||||
console.log(`Certificate download URL: ${href}`);
|
||||
|
||||
// Verify URL format matches security pattern
|
||||
expect(href).toMatch(/\/hvac-certificate\/[a-zA-Z0-9]{32}$/);
|
||||
console.log('✅ Certificate security URL format is correct');
|
||||
} else {
|
||||
console.log('No certificate download links found');
|
||||
}
|
||||
} else {
|
||||
console.log('No certificate table found - may need to generate certificates first');
|
||||
}
|
||||
});
|
||||
|
||||
test('should test direct certificate URL access', async ({ page }) => {
|
||||
console.log('=== Testing Direct Certificate URL Access ===');
|
||||
|
||||
// Test with an invalid token
|
||||
const invalidToken = 'a'.repeat(32);
|
||||
const invalidUrl = `${STAGING_URL}/hvac-certificate/${invalidToken}`;
|
||||
|
||||
console.log(`Testing invalid certificate URL: ${invalidUrl}`);
|
||||
|
||||
const response = await page.goto(invalidUrl);
|
||||
|
||||
// Should get an error page for invalid token
|
||||
if (response) {
|
||||
const status = response.status();
|
||||
console.log(`Response status: ${status}`);
|
||||
|
||||
// Could be 404, 403, or a custom error page
|
||||
expect([200, 403, 404]).toContain(status);
|
||||
|
||||
if (status === 200) {
|
||||
// Check for error message in content
|
||||
const content = await page.textContent('body');
|
||||
expect(content).toMatch(/invalid|expired|not found/i);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Invalid certificate URL properly handled');
|
||||
});
|
||||
});
|
||||
|
|
@ -297,10 +297,91 @@
|
|||
/* Search results indicator */
|
||||
.hvac-search-results {
|
||||
background-color: #f0f7ff;
|
||||
border-left: 4px solid #2271b1;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
/* Certificate Preview Modal */
|
||||
#hvac-certificate-preview-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
#hvac-certificate-preview-modal .hvac-modal-content {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
margin: 3% auto;
|
||||
width: 90%;
|
||||
max-width: 1000px;
|
||||
height: 85%;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#hvac-certificate-preview-modal .hvac-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
#hvac-certificate-preview-modal .hvac-modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#hvac-certificate-preview-modal .hvac-modal-close {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#hvac-certificate-preview-modal .hvac-modal-close:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#hvac-certificate-preview-modal .hvac-modal-body {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#hvac-certificate-preview-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Certificate Preview Buttons */
|
||||
.hvac-certificate-previews {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.hvac-certificate-previews h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hvac-preview-certificate {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.hvac-search-results p {
|
||||
|
|
|
|||
|
|
@ -199,9 +199,298 @@
|
|||
}
|
||||
};
|
||||
|
||||
// Generate Certificates AJAX functionality
|
||||
const GenerateCertificates = {
|
||||
/**
|
||||
* Initialize generate certificates functionality
|
||||
*/
|
||||
init: function() {
|
||||
// Event selection
|
||||
$('#event_id').on('change', this.loadAttendees);
|
||||
|
||||
// Certificate generation
|
||||
$('#generate-certificates-form').on('submit', this.generateCertificates);
|
||||
|
||||
// Helper buttons
|
||||
$('#select-all-attendees').on('click', this.selectAllAttendees);
|
||||
$('#select-checked-in').on('click', this.selectCheckedInAttendees);
|
||||
$('#deselect-all-attendees').on('click', this.deselectAllAttendees);
|
||||
|
||||
// Certificate preview (use event delegation for dynamic buttons)
|
||||
$(document).on('click', '.hvac-preview-certificate', this.showCertificatePreview);
|
||||
$(document).on('click', '.hvac-modal-close', this.closeCertificatePreview);
|
||||
$(document).on('click', '#hvac-certificate-preview-modal', function(e) {
|
||||
if (e.target === this) {
|
||||
GenerateCertificates.closeCertificatePreview(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Load attendees via AJAX when event is selected
|
||||
*/
|
||||
loadAttendees: function(e) {
|
||||
const eventId = $(this).val();
|
||||
|
||||
if (!eventId) {
|
||||
$('#step-select-attendees').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading
|
||||
$('#attendees-loading').show();
|
||||
$('#attendees-content').hide();
|
||||
|
||||
$.ajax({
|
||||
url: hvacCertificateData.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'hvac_get_event_attendees',
|
||||
event_id: eventId,
|
||||
nonce: hvacCertificateData.generateNonce
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
GenerateCertificates.renderAttendees(response.data.attendees, response.data.event_title);
|
||||
$('#step-select-attendees').show();
|
||||
} else {
|
||||
alert('Error: ' + response.data.message);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('Failed to load attendees. Please try again.');
|
||||
},
|
||||
complete: function() {
|
||||
$('#attendees-loading').hide();
|
||||
$('#attendees-content').show();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Render attendees table
|
||||
*/
|
||||
renderAttendees: function(attendees, eventTitle) {
|
||||
if (attendees.length === 0) {
|
||||
$('#attendees-table-container').html('<p class="hvac-empty-state">This event has no attendees.</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
let tableHtml = `
|
||||
<div class="hvac-form-group">
|
||||
<div class="hvac-table-actions">
|
||||
<button type="button" class="hvac-button hvac-secondary" id="select-all-attendees">Select All</button>
|
||||
<button type="button" class="hvac-button hvac-secondary" id="select-checked-in">Select Checked-In Only</button>
|
||||
<button type="button" class="hvac-button hvac-secondary" id="deselect-all-attendees">Deselect All</button>
|
||||
</div>
|
||||
|
||||
<div class="hvac-attendees-table-wrapper">
|
||||
<table class="hvac-attendees-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="hvac-checkbox-column">
|
||||
<input type="checkbox" id="select-all-checkbox">
|
||||
</th>
|
||||
<th>Attendee</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Certificate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
attendees.forEach(function(attendee) {
|
||||
const checkedInClass = attendee.check_in ? 'hvac-checked-in' : '';
|
||||
const statusClass = attendee.check_in ? 'hvac-status-checked-in' : 'hvac-status-not-checked-in';
|
||||
const statusText = attendee.check_in ? 'Checked In' : 'Not Checked In';
|
||||
const certificateStatus = attendee.has_certificate ? 'Certificate Issued' : 'No Certificate';
|
||||
const hasCertClass = attendee.has_certificate ? 'hvac-has-certificate' : '';
|
||||
|
||||
tableHtml += `
|
||||
<tr class="${hasCertClass} ${checkedInClass}">
|
||||
<td>
|
||||
${!attendee.has_certificate ?
|
||||
`<input type="checkbox" name="attendee_ids[]" value="${attendee.attendee_id}" class="attendee-checkbox" ${attendee.check_in ? 'checked' : ''}>` :
|
||||
''
|
||||
}
|
||||
</td>
|
||||
<td>${attendee.holder_name}</td>
|
||||
<td>${attendee.holder_email}</td>
|
||||
<td><span class="${statusClass}">${statusText}</span></td>
|
||||
<td>${certificateStatus}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
tableHtml += `
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('#attendees-table-container').html(tableHtml);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate certificates via AJAX
|
||||
*/
|
||||
generateCertificates: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const $form = $(this);
|
||||
const formData = $form.serialize();
|
||||
|
||||
// Show loading
|
||||
const $submitBtn = $form.find('button[type="submit"]');
|
||||
$submitBtn.prop('disabled', true).text('Generating...');
|
||||
|
||||
// Hide previous messages
|
||||
$('.hvac-success-message, .hvac-errors').remove();
|
||||
|
||||
$.ajax({
|
||||
url: hvacCertificateData.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: formData + '&action=hvac_generate_certificates&nonce=' + hvacCertificateData.generateNonce,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Show success message with preview option
|
||||
let successMessage = `
|
||||
<div class="hvac-success-message">
|
||||
<p>${response.data.message}</p>
|
||||
<p><a href="/certificate-reports/" class="hvac-button hvac-primary">View All Certificates</a></p>
|
||||
`;
|
||||
|
||||
// Add preview buttons if certificates were generated
|
||||
if (response.data.preview_urls && response.data.preview_urls.length > 0) {
|
||||
successMessage += '<div class="hvac-certificate-previews"><h4>Preview Generated Certificates:</h4>';
|
||||
response.data.preview_urls.forEach(function(preview) {
|
||||
successMessage += `
|
||||
<button type="button" class="hvac-button hvac-secondary hvac-preview-certificate"
|
||||
data-url="${preview.preview_url}"
|
||||
data-attendee="${preview.attendee_name}">
|
||||
Preview - ${preview.attendee_name}
|
||||
</button>
|
||||
`;
|
||||
});
|
||||
successMessage += '</div>';
|
||||
}
|
||||
|
||||
successMessage += '</div>';
|
||||
$form.before(successMessage);
|
||||
|
||||
// Reload attendees to update certificate status
|
||||
$('#event_id').trigger('change');
|
||||
} else {
|
||||
// Show error message
|
||||
$form.before(`
|
||||
<div class="hvac-errors">
|
||||
<p class="hvac-error">${response.data.message}</p>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$form.before(`
|
||||
<div class="hvac-errors">
|
||||
<p class="hvac-error">Failed to generate certificates. Please try again.</p>
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
complete: function() {
|
||||
$submitBtn.prop('disabled', false).text('Generate Certificates');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Select all attendees
|
||||
*/
|
||||
selectAllAttendees: function(e) {
|
||||
e.preventDefault();
|
||||
$('.attendee-checkbox').prop('checked', true);
|
||||
$('#select-all-checkbox').prop('checked', true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Select only checked-in attendees
|
||||
*/
|
||||
selectCheckedInAttendees: function(e) {
|
||||
e.preventDefault();
|
||||
$('.attendee-checkbox').each(function() {
|
||||
const $row = $(this).closest('tr');
|
||||
$(this).prop('checked', $row.hasClass('hvac-checked-in'));
|
||||
});
|
||||
$('#select-all-checkbox').prop('checked', false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deselect all attendees
|
||||
*/
|
||||
deselectAllAttendees: function(e) {
|
||||
e.preventDefault();
|
||||
$('.attendee-checkbox').prop('checked', false);
|
||||
$('#select-all-checkbox').prop('checked', false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show certificate preview modal
|
||||
*/
|
||||
showCertificatePreview: function(e) {
|
||||
e.preventDefault();
|
||||
const $button = $(this);
|
||||
const url = $button.data('url');
|
||||
const attendeeName = $button.data('attendee');
|
||||
|
||||
if (!url) {
|
||||
alert('Preview URL not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create modal if it doesn't exist
|
||||
if ($('#hvac-certificate-preview-modal').length === 0) {
|
||||
$('body').append(`
|
||||
<div id="hvac-certificate-preview-modal" class="hvac-modal">
|
||||
<div class="hvac-modal-content">
|
||||
<div class="hvac-modal-header">
|
||||
<h3>Certificate Preview</h3>
|
||||
<span class="hvac-modal-close">×</span>
|
||||
</div>
|
||||
<div class="hvac-modal-body">
|
||||
<iframe id="hvac-certificate-preview-iframe" src="" width="100%" height="600px" frameborder="0"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Update modal title and iframe source
|
||||
$('#hvac-certificate-preview-modal h3').text(`Certificate Preview - ${attendeeName}`);
|
||||
$('#hvac-certificate-preview-iframe').attr('src', url);
|
||||
|
||||
// Show modal
|
||||
$('#hvac-certificate-preview-modal').show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Close certificate preview modal
|
||||
*/
|
||||
closeCertificatePreview: function(e) {
|
||||
e.preventDefault();
|
||||
$('#hvac-certificate-preview-modal').hide();
|
||||
$('#hvac-certificate-preview-iframe').attr('src', '');
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize when document is ready
|
||||
$(document).ready(function() {
|
||||
CertificateActions.init();
|
||||
|
||||
// Initialize Generate Certificates functionality if on that page
|
||||
if ($('#generate-certificates-form').length) {
|
||||
GenerateCertificates.init();
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ class HVAC_Certificate_AJAX_Handler {
|
|||
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'));
|
||||
add_action('wp_ajax_hvac_generate_certificates', array($this, 'generate_certificates'));
|
||||
add_action('wp_ajax_hvac_get_event_attendees', array($this, 'get_event_attendees'));
|
||||
|
||||
// Enqueue scripts
|
||||
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
||||
|
|
@ -90,7 +92,7 @@ class HVAC_Certificate_AJAX_Handler {
|
|||
*/
|
||||
public function enqueue_scripts() {
|
||||
// Only load on certificate pages
|
||||
if (is_page('certificate-reports')) {
|
||||
if (is_page('certificate-reports') || is_page('generate-certificates')) {
|
||||
// Enqueue certificate actions JS
|
||||
wp_enqueue_script(
|
||||
'hvac-certificate-actions-js',
|
||||
|
|
@ -105,7 +107,8 @@ class HVAC_Certificate_AJAX_Handler {
|
|||
'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')
|
||||
'revokeNonce' => wp_create_nonce('hvac_revoke_certificate'),
|
||||
'generateNonce' => wp_create_nonce('hvac_generate_certificates')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -355,4 +358,168 @@ class HVAC_Certificate_AJAX_Handler {
|
|||
wp_send_json_error(array('message' => 'Failed to revoke certificate'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for getting event attendees.
|
||||
*/
|
||||
public function get_event_attendees() {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_generate_certificates')) {
|
||||
wp_send_json_error(array('message' => 'Security check failed'));
|
||||
}
|
||||
|
||||
$event_id = isset($_POST['event_id']) ? absint($_POST['event_id']) : 0;
|
||||
|
||||
if (!$event_id) {
|
||||
wp_send_json_error(array('message' => 'Event ID is required'));
|
||||
}
|
||||
|
||||
// Check user permissions
|
||||
$event = get_post($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 event'));
|
||||
}
|
||||
|
||||
// Get attendees using direct database query (same as Generate Certificates template)
|
||||
global $wpdb;
|
||||
$tec_attendees = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT
|
||||
p.ID as attendee_id,
|
||||
p.post_parent as event_id,
|
||||
COALESCE(tec_full_name.meta_value, tpp_full_name.meta_value, tickets_full_name.meta_value, 'Unknown Attendee') as holder_name,
|
||||
COALESCE(tec_email.meta_value, tpp_email.meta_value, tickets_email.meta_value, tpp_attendee_email.meta_value, 'no-email@example.com') as holder_email,
|
||||
COALESCE(checked_in.meta_value, '0') as check_in
|
||||
FROM {$wpdb->posts} p
|
||||
LEFT JOIN {$wpdb->postmeta} tec_full_name ON p.ID = tec_full_name.post_id AND tec_full_name.meta_key = '_tec_tickets_commerce_full_name'
|
||||
LEFT JOIN {$wpdb->postmeta} tpp_full_name ON p.ID = tpp_full_name.post_id AND tpp_full_name.meta_key = '_tribe_tpp_full_name'
|
||||
LEFT JOIN {$wpdb->postmeta} tickets_full_name ON p.ID = tickets_full_name.post_id AND tickets_full_name.meta_key = '_tribe_tickets_full_name'
|
||||
LEFT JOIN {$wpdb->postmeta} tec_email ON p.ID = tec_email.post_id AND tec_email.meta_key = '_tec_tickets_commerce_email'
|
||||
LEFT JOIN {$wpdb->postmeta} tpp_email ON p.ID = tpp_email.post_id AND tpp_email.meta_key = '_tribe_tpp_email'
|
||||
LEFT JOIN {$wpdb->postmeta} tickets_email ON p.ID = tickets_email.post_id AND tickets_email.meta_key = '_tribe_tickets_email'
|
||||
LEFT JOIN {$wpdb->postmeta} tpp_attendee_email ON p.ID = tpp_attendee_email.post_id AND tpp_attendee_email.meta_key = '_tribe_tpp_attendee_email'
|
||||
LEFT JOIN {$wpdb->postmeta} checked_in ON p.ID = checked_in.post_id AND checked_in.meta_key = '_tribe_tickets_attendee_checked_in'
|
||||
WHERE p.post_type IN ('tec_tc_attendee', 'tribe_tpp_attendees')
|
||||
AND p.post_parent = %d
|
||||
ORDER BY p.ID ASC",
|
||||
$event_id
|
||||
));
|
||||
|
||||
$attendees = array();
|
||||
foreach ($tec_attendees as $attendee) {
|
||||
// Check if certificate already exists
|
||||
$has_certificate = $this->certificate_manager->certificate_exists($event_id, $attendee->attendee_id);
|
||||
|
||||
$attendees[] = array(
|
||||
'attendee_id' => $attendee->attendee_id,
|
||||
'event_id' => $attendee->event_id,
|
||||
'holder_name' => $attendee->holder_name,
|
||||
'holder_email' => $attendee->holder_email,
|
||||
'check_in' => intval($attendee->check_in),
|
||||
'has_certificate' => $has_certificate
|
||||
);
|
||||
}
|
||||
|
||||
wp_send_json_success(array(
|
||||
'attendees' => $attendees,
|
||||
'event_title' => $event->post_title
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for generating certificates.
|
||||
*/
|
||||
public function generate_certificates() {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_generate_certificates')) {
|
||||
wp_send_json_error(array('message' => 'Security check failed'));
|
||||
}
|
||||
|
||||
$event_id = isset($_POST['event_id']) ? absint($_POST['event_id']) : 0;
|
||||
$attendee_ids = isset($_POST['attendee_ids']) && is_array($_POST['attendee_ids']) ? array_map('absint', $_POST['attendee_ids']) : array();
|
||||
$checked_in_only = isset($_POST['checked_in_only']) && $_POST['checked_in_only'] === 'yes';
|
||||
|
||||
if (!$event_id) {
|
||||
wp_send_json_error(array('message' => 'Event ID is required'));
|
||||
}
|
||||
|
||||
if (empty($attendee_ids)) {
|
||||
wp_send_json_error(array('message' => 'Please select at least one attendee'));
|
||||
}
|
||||
|
||||
// Check user permissions
|
||||
$event = get_post($event_id);
|
||||
if (!$event || !current_user_can('edit_post', $event->ID)) {
|
||||
wp_send_json_error(array('message' => 'You do not have permission to generate certificates for this event'));
|
||||
}
|
||||
|
||||
// Load certificate generator
|
||||
if (!class_exists('HVAC_Certificate_Generator')) {
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/certificates/class-certificate-generator.php';
|
||||
}
|
||||
$certificate_generator = HVAC_Certificate_Generator::instance();
|
||||
|
||||
// Generate certificates in batch
|
||||
$generation_results = $certificate_generator->generate_certificates_batch(
|
||||
$event_id,
|
||||
$attendee_ids,
|
||||
array(), // Custom data (none for now)
|
||||
get_current_user_id(), // Generated by current user
|
||||
$checked_in_only // Only for checked-in attendees if selected
|
||||
);
|
||||
|
||||
// Format response message
|
||||
$message_parts = array();
|
||||
if ($generation_results['success'] > 0) {
|
||||
$message_parts[] = 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']);
|
||||
}
|
||||
|
||||
if ($generation_results['success'] > 0) {
|
||||
// Generate preview URLs for the certificates just created
|
||||
$preview_urls = array();
|
||||
if (!empty($generation_results['certificate_ids'])) {
|
||||
foreach ($generation_results['certificate_ids'] as $certificate_id) {
|
||||
$certificate = $this->certificate_manager->get_certificate($certificate_id);
|
||||
if ($certificate && $certificate->file_path) {
|
||||
// Generate secure download token for preview
|
||||
$security = HVAC_Certificate_Security::instance();
|
||||
$preview_url = $security->generate_download_token($certificate_id, array(
|
||||
'file_path' => $certificate->file_path,
|
||||
'event_name' => get_the_title($certificate->event_id),
|
||||
'attendee_name' => $certificate->attendee_name
|
||||
));
|
||||
if ($preview_url) {
|
||||
$preview_urls[] = array(
|
||||
'certificate_id' => $certificate_id,
|
||||
'attendee_name' => $certificate->attendee_name,
|
||||
'preview_url' => $preview_url
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success(array(
|
||||
'message' => implode(' ', $message_parts),
|
||||
'results' => $generation_results,
|
||||
'preview_urls' => $preview_urls
|
||||
));
|
||||
} else {
|
||||
wp_send_json_error(array(
|
||||
'message' => 'Failed to generate certificates. ' . implode(' ', $message_parts),
|
||||
'results' => $generation_results
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -203,6 +203,11 @@ class HVAC_Community_Events {
|
|||
HVAC_Certificate_AJAX_Handler::instance();
|
||||
}
|
||||
|
||||
// Initialize certificate security
|
||||
if (class_exists('HVAC_Certificate_Security')) {
|
||||
HVAC_Certificate_Security::instance();
|
||||
}
|
||||
|
||||
// Initialize event form handler
|
||||
if (class_exists('HVAC_Community_Events\Event_Form_Handler')) {
|
||||
new \HVAC_Community_Events\Event_Form_Handler();
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ wp_enqueue_style(
|
|||
<?php if (empty($events)) : ?>
|
||||
<p class="hvac-empty-state">You don't have any events. <a href="<?php echo esc_url(get_permalink(get_page_by_path('manage-event'))); ?>">Create an event</a> first.</p>
|
||||
<?php else : ?>
|
||||
<form method="get" class="hvac-form">
|
||||
<div class="hvac-form">
|
||||
<div class="hvac-form-group">
|
||||
<label for="event_id">Select an event:</label>
|
||||
<select name="event_id" id="event_id" class="hvac-select" required>
|
||||
|
|
@ -242,121 +242,56 @@ wp_enqueue_style(
|
|||
<?php foreach ($events as $event) : ?>
|
||||
<option value="<?php echo esc_attr($event->ID); ?>" <?php selected($event_id, $event->ID); ?>>
|
||||
<?php echo esc_html($event->post_title); ?> -
|
||||
<?php echo esc_html(tribe_get_start_date($event->ID, false, get_option('date_format'))); ?>
|
||||
<?php echo esc_html(date('M j, Y', strtotime($event->post_date))); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="hvac-form-group">
|
||||
<button type="submit" class="hvac-button hvac-primary">Select Event</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($event_id > 0) : ?>
|
||||
<!-- Step 2: Select Attendees -->
|
||||
<div class="hvac-section hvac-step-section" id="step-select-attendees">
|
||||
<h2>Step 2: Select Attendees</h2>
|
||||
|
||||
<?php if (empty($attendees)) : ?>
|
||||
<p class="hvac-empty-state">This event has no attendees. Sell tickets or add attendees to your event first.</p>
|
||||
<?php else : ?>
|
||||
<form method="post" class="hvac-form">
|
||||
<?php wp_nonce_field('hvac_generate_certificates', 'hvac_certificate_nonce'); ?>
|
||||
<input type="hidden" name="event_id" value="<?php echo esc_attr($event_id); ?>">
|
||||
|
||||
<div class="hvac-form-group">
|
||||
<div class="hvac-table-actions">
|
||||
<button type="button" class="hvac-button hvac-secondary" id="select-all-attendees">Select All</button>
|
||||
<button type="button" class="hvac-button hvac-secondary" id="select-checked-in">Select Checked-In Only</button>
|
||||
<button type="button" class="hvac-button hvac-secondary" id="deselect-all-attendees">Deselect All</button>
|
||||
</div>
|
||||
|
||||
<div class="hvac-form-options">
|
||||
<label class="hvac-checkbox-label">
|
||||
<input type="checkbox" name="checked_in_only" value="yes" id="checked-in-only-checkbox">
|
||||
Generate certificates only for checked-in attendees
|
||||
</label>
|
||||
<p class="hvac-form-help">Check this option to only generate certificates for attendees who have been marked as checked in to the event.</p>
|
||||
</div>
|
||||
|
||||
<div class="hvac-attendees-table-wrapper">
|
||||
<table class="hvac-attendees-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="hvac-checkbox-column">
|
||||
<input type="checkbox" id="select-all-checkbox">
|
||||
</th>
|
||||
<th>Attendee</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Certificate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($attendees as $attendee) :
|
||||
// Get attendee info
|
||||
$attendee_id = $attendee['attendee_id'];
|
||||
$attendee_name = isset($attendee['holder_name']) ? $attendee['holder_name'] : '';
|
||||
$attendee_email = isset($attendee['holder_email']) ? $attendee['holder_email'] : '';
|
||||
$checked_in = isset($attendee['check_in']) && $attendee['check_in'] === 1;
|
||||
|
||||
// Check if certificate already exists
|
||||
$has_certificate = $certificate_manager->certificate_exists($event_id, $attendee_id);
|
||||
$certificate_status = $has_certificate ? 'Certificate Issued' : 'No Certificate';
|
||||
|
||||
// Status class
|
||||
$status_class = $checked_in ? 'hvac-status-checked-in' : 'hvac-status-not-checked-in';
|
||||
$status_text = $checked_in ? 'Checked In' : 'Not Checked In';
|
||||
?>
|
||||
<tr class="<?php echo $has_certificate ? 'hvac-has-certificate' : ''; ?> <?php echo $checked_in ? 'hvac-checked-in' : ''; ?>">
|
||||
<td>
|
||||
<?php if (!$has_certificate) : ?>
|
||||
<input type="checkbox" name="attendee_ids[]" value="<?php echo esc_attr($attendee_id); ?>" class="attendee-checkbox" <?php checked($checked_in); ?>>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo esc_html($attendee_name); ?></td>
|
||||
<td><?php echo esc_html($attendee_email); ?></td>
|
||||
<td><span class="<?php echo esc_attr($status_class); ?>"><?php echo esc_html($status_text); ?></span></td>
|
||||
<td><?php echo esc_html($certificate_status); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hvac-form-group">
|
||||
<div class="hvac-certificate-preview">
|
||||
<h3>Certificate Preview</h3>
|
||||
<p>Certificates will be generated based on your template settings.</p>
|
||||
|
||||
<?php
|
||||
// Get template info for preview
|
||||
$templates = $certificate_template->get_templates();
|
||||
$default_template = 'default';
|
||||
|
||||
if (!empty($templates)) {
|
||||
echo '<p>Template: ' . esc_html(ucfirst($default_template)) . '</p>';
|
||||
echo '<p class="hvac-certificate-preview-note">A professional certificate will be generated based on the default template.</p>';
|
||||
} else {
|
||||
echo '<p>Preview not available</p>';
|
||||
}
|
||||
?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hvac-form-actions">
|
||||
<a href="<?php echo esc_url(remove_query_arg('event_id')); ?>" class="hvac-button hvac-secondary">Back to Event Selection</a>
|
||||
<button type="submit" name="generate_certificates" class="hvac-button hvac-primary">Generate Certificates</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<!-- Step 2: Select Attendees (AJAX loaded) -->
|
||||
<div class="hvac-section hvac-step-section" id="step-select-attendees" style="display: none;">
|
||||
<h2>Step 2: Select Attendees</h2>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div id="attendees-loading" style="display: none;">
|
||||
<p>Loading attendees...</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Attendees content -->
|
||||
<div id="attendees-content">
|
||||
<form id="generate-certificates-form" class="hvac-form">
|
||||
<input type="hidden" name="event_id" id="selected_event_id" value="">
|
||||
|
||||
<div class="hvac-form-group">
|
||||
<div class="hvac-form-options">
|
||||
<label class="hvac-checkbox-label">
|
||||
<input type="checkbox" name="checked_in_only" value="yes" id="checked-in-only-checkbox">
|
||||
Generate certificates only for checked-in attendees
|
||||
</label>
|
||||
<p class="hvac-form-help">Check this option to only generate certificates for attendees who have been marked as checked in to the event.</p>
|
||||
</div>
|
||||
|
||||
<!-- Attendees table will be loaded here via AJAX -->
|
||||
<div id="attendees-table-container"></div>
|
||||
</div>
|
||||
|
||||
<div class="hvac-form-group">
|
||||
<div class="hvac-certificate-preview">
|
||||
<h3>Certificate Preview</h3>
|
||||
<p>Certificates will be generated based on your template settings.</p>
|
||||
<p class="hvac-certificate-preview-note">A professional certificate will be generated based on the default template.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hvac-form-actions">
|
||||
<button type="submit" name="generate_certificates" class="hvac-button hvac-primary">Generate Certificates</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hvac-section hvac-info-section">
|
||||
<h2>Certificate Management Tools</h2>
|
||||
|
|
|
|||
Loading…
Reference in a new issue