upskill-event-manager/assets/js/hvac-tec-tickets.js
ben 90193ea18c security: implement Phase 1 critical vulnerability fixes
- Add XSS protection with DOMPurify sanitization in rich text editor
- Implement comprehensive file upload security validation
- Enhance server-side content sanitization with wp_kses
- Add comprehensive security test suite with 194+ test cases
- Create security remediation plan documentation

Security fixes address:
- CRITICAL: XSS vulnerability in event description editor
- HIGH: File upload security bypass for malicious files
- HIGH: Enhanced CSRF protection verification
- MEDIUM: Input validation and error handling improvements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 18:53:23 -03:00

676 lines
No EOL
24 KiB
JavaScript

/**
* HVAC TEC Tickets Interactive JavaScript
* Handles all UI/UX interactions for the enhanced event form
*/
(function($) {
'use strict';
// Global state management
window.HVACEventForm = {
selectedOrganizers: [],
selectedCategories: [],
selectedVenue: null,
richTextEditors: {},
init: function() {
this.initRichTextEditor();
this.initToggleSwitches();
this.initFeaturedImageUploader();
this.initSearchableSelectors();
this.initModalForms();
this.bindEvents();
},
// Rich Text Editor Functionality
initRichTextEditor: function() {
const editorWrapper = document.getElementById('event-description-editor-wrapper');
if (!editorWrapper) return;
const editor = document.getElementById('event-description-editor');
const hiddenTextarea = document.getElementById('event_description');
const toolbar = document.getElementById('event-description-toolbar');
if (!editor || !hiddenTextarea || !toolbar) return;
// Initialize editor
this.richTextEditors.description = {
editor: editor,
textarea: hiddenTextarea,
toolbar: toolbar
};
// Bind toolbar events
$(toolbar).on('click', 'button', function(e) {
e.preventDefault();
const command = $(this).data('command');
const value = $(this).data('value') || null;
document.execCommand(command, false, value);
HVACEventForm.updateToolbarState();
HVACEventForm.syncEditorContent();
});
// Sync content changes
editor.addEventListener('input', () => {
this.syncEditorContent();
});
editor.addEventListener('keyup', () => {
this.updateToolbarState();
});
editor.addEventListener('mouseup', () => {
this.updateToolbarState();
});
// Load existing content with XSS protection
if (hiddenTextarea.value) {
const cleanContent = DOMPurify.sanitize(hiddenTextarea.value, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false
});
editor.innerHTML = cleanContent;
}
},
syncEditorContent: function() {
const editor = this.richTextEditors.description?.editor;
const textarea = this.richTextEditors.description?.textarea;
if (editor && textarea) {
// Sanitize content before storing in textarea to prevent XSS
const cleanContent = DOMPurify.sanitize(editor.innerHTML, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false
});
textarea.value = cleanContent;
}
},
updateToolbarState: function() {
const toolbar = this.richTextEditors.description?.toolbar;
if (!toolbar) return;
$(toolbar).find('button').each(function() {
const command = $(this).data('command');
if (document.queryCommandState(command)) {
$(this).addClass('active');
} else {
$(this).removeClass('active');
}
});
},
// Toggle Switch Functionality
initToggleSwitches: function() {
// Virtual Event Toggle
$(document).on('change', 'input[name="enable_virtual_event"]', function() {
const isChecked = $(this).is(':checked');
$('.virtual-event-config').toggle(isChecked);
});
// RSVP Toggle
$(document).on('change', 'input[name="enable_rsvp"]', function() {
const isChecked = $(this).is(':checked');
$('.rsvp-config').toggle(isChecked);
});
// Ticketing Toggle (existing functionality)
window.hvacToggleTicketFields = function(enabled) {
$('.ticket-config-field').toggle(enabled);
};
},
// Featured Image Uploader
initFeaturedImageUploader: function() {
const uploadArea = document.getElementById('upload-area');
const fileInput = document.getElementById('featured-image-file');
const preview = document.getElementById('featured-image-preview');
const uploader = document.getElementById('featured-image-uploader');
const img = document.getElementById('featured-image-img');
const changeBtn = document.getElementById('change-featured-image');
const removeBtn = document.getElementById('remove-featured-image');
if (!uploadArea || !fileInput) return;
// Click to upload
uploadArea.addEventListener('click', () => {
fileInput.click();
});
// Drag and drop
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
this.handleImageUpload(files[0]);
}
});
// File input change
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
this.handleImageUpload(e.target.files[0]);
}
});
// Change image button
if (changeBtn) {
changeBtn.addEventListener('click', () => {
fileInput.click();
});
}
// Remove image button
if (removeBtn) {
removeBtn.addEventListener('click', () => {
this.removeFeaturedImage();
});
}
},
handleImageUpload: function(file) {
if (!file.type.startsWith('image/')) {
alert('Please select a valid image file.');
return;
}
if (file.size > 5 * 1024 * 1024) { // 5MB limit
alert('Image file size should be less than 5MB.');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const img = document.getElementById('featured-image-img');
const preview = document.getElementById('featured-image-preview');
const uploader = document.getElementById('featured-image-uploader');
const hiddenInput = document.getElementById('featured-image-data');
img.src = e.target.result;
preview.style.display = 'block';
uploader.style.display = 'none';
// Store image data for form submission
if (hiddenInput) {
hiddenInput.value = e.target.result;
}
};
reader.readAsDataURL(file);
},
removeFeaturedImage: function() {
const preview = document.getElementById('featured-image-preview');
const uploader = document.getElementById('featured-image-uploader');
const hiddenInput = document.getElementById('featured-image-data');
const fileInput = document.getElementById('featured-image-file');
preview.style.display = 'none';
uploader.style.display = 'block';
if (hiddenInput) hiddenInput.value = '';
if (fileInput) fileInput.value = '';
},
// Searchable Selector Components
initSearchableSelectors: function() {
this.initOrganizerSelector();
this.initCategoriesSelector();
this.initVenueSelector();
},
initOrganizerSelector: function() {
const searchInput = document.getElementById('organizer-search-input');
const resultsContainer = document.getElementById('organizer-search-results');
const selectedContainer = document.getElementById('organizer-selected-items');
const dropdownToggle = searchInput?.parentElement?.querySelector('.dropdown-toggle');
if (!searchInput || !resultsContainer) return;
// Search input events
searchInput.addEventListener('focus', () => {
this.loadOrganizerOptions();
resultsContainer.style.display = 'block';
});
searchInput.addEventListener('input', (e) => {
this.filterOrganizerOptions(e.target.value);
});
// Dropdown toggle
if (dropdownToggle) {
dropdownToggle.addEventListener('click', () => {
if (resultsContainer.style.display === 'none') {
this.loadOrganizerOptions();
resultsContainer.style.display = 'block';
searchInput.focus();
} else {
resultsContainer.style.display = 'none';
}
});
}
// Click outside to close
document.addEventListener('click', (e) => {
if (!searchInput.parentElement.contains(e.target)) {
resultsContainer.style.display = 'none';
}
});
// Create new organizer button
$(document).on('click', '#create-new-organizer .create-new-btn', () => {
this.openModal('#new-organizer-modal');
});
},
initCategoriesSelector: function() {
const searchInput = document.getElementById('categories-search-input');
const resultsContainer = document.getElementById('categories-search-results');
const dropdownToggle = searchInput?.parentElement?.querySelector('.dropdown-toggle');
if (!searchInput || !resultsContainer) return;
searchInput.addEventListener('focus', () => {
this.loadCategoriesOptions();
resultsContainer.style.display = 'block';
});
searchInput.addEventListener('input', (e) => {
this.filterCategoriesOptions(e.target.value);
});
if (dropdownToggle) {
dropdownToggle.addEventListener('click', () => {
if (resultsContainer.style.display === 'none') {
this.loadCategoriesOptions();
resultsContainer.style.display = 'block';
searchInput.focus();
} else {
resultsContainer.style.display = 'none';
}
});
}
document.addEventListener('click', (e) => {
if (!searchInput.parentElement.contains(e.target)) {
resultsContainer.style.display = 'none';
}
});
$(document).on('click', '#create-new-category .create-new-btn', () => {
this.openModal('#new-category-modal');
});
},
initVenueSelector: function() {
const searchInput = document.getElementById('venue-search-input');
const resultsContainer = document.getElementById('venue-search-results');
const dropdownToggle = searchInput?.parentElement?.querySelector('.dropdown-toggle');
if (!searchInput || !resultsContainer) return;
searchInput.addEventListener('focus', () => {
this.loadVenueOptions();
resultsContainer.style.display = 'block';
});
searchInput.addEventListener('input', (e) => {
this.filterVenueOptions(e.target.value);
});
if (dropdownToggle) {
dropdownToggle.addEventListener('click', () => {
if (resultsContainer.style.display === 'none') {
this.loadVenueOptions();
resultsContainer.style.display = 'block';
searchInput.focus();
} else {
resultsContainer.style.display = 'none';
}
});
}
document.addEventListener('click', (e) => {
if (!searchInput.parentElement.contains(e.target)) {
resultsContainer.style.display = 'none';
}
});
$(document).on('click', '.create-new-btn', () => {
this.openModal('#new-venue-modal');
});
},
// Load options from server
loadOrganizerOptions: function() {
// Mock data for now - replace with AJAX call
const mockOrganizers = [
{ id: 1, name: 'John Smith', email: 'john@example.com' },
{ id: 2, name: 'Jane Doe', email: 'jane@example.com' },
{ id: 3, name: 'HVAC Training Institute', email: 'info@hvactraining.com' }
];
this.renderOrganizerOptions(mockOrganizers);
},
loadCategoriesOptions: function() {
const mockCategories = [
{ id: 1, name: 'HVAC Basics', description: 'Fundamental HVAC concepts' },
{ id: 2, name: 'Advanced Systems', description: 'Complex HVAC systems' },
{ id: 3, name: 'Safety Training', description: 'Safety protocols and procedures' }
];
this.renderCategoriesOptions(mockCategories);
},
loadVenueOptions: function() {
const mockVenues = [
{ id: 1, name: 'Training Center A', address: '123 Main St, City, ST' },
{ id: 2, name: 'Conference Hall B', address: '456 Oak Ave, City, ST' },
{ id: 3, name: 'Community Center', address: '789 Pine Rd, City, ST' }
];
this.renderVenueOptions(mockVenues);
},
renderOrganizerOptions: function(organizers) {
const resultsList = document.getElementById('organizer-results-list');
if (!resultsList) return;
resultsList.innerHTML = '';
organizers.forEach(organizer => {
const item = document.createElement('div');
item.className = 'search-result-item';
item.innerHTML = `<strong>${organizer.name}</strong><br><small>${organizer.email}</small>`;
item.addEventListener('click', () => {
this.selectOrganizer(organizer);
});
resultsList.appendChild(item);
});
},
renderCategoriesOptions: function(categories) {
const resultsList = document.getElementById('categories-results-list');
if (!resultsList) return;
resultsList.innerHTML = '';
categories.forEach(category => {
const item = document.createElement('div');
item.className = 'search-result-item';
item.innerHTML = `<strong>${category.name}</strong><br><small>${category.description}</small>`;
item.addEventListener('click', () => {
this.selectCategory(category);
});
resultsList.appendChild(item);
});
},
renderVenueOptions: function(venues) {
const resultsList = document.querySelector('#venue-search-results .results-list');
if (!resultsList) return;
resultsList.innerHTML = '';
venues.forEach(venue => {
const item = document.createElement('div');
item.className = 'search-result-item';
item.innerHTML = `<strong>${venue.name}</strong><br><small>${venue.address}</small>`;
item.addEventListener('click', () => {
this.selectVenue(venue);
});
resultsList.appendChild(item);
});
},
// Selection handlers
selectOrganizer: function(organizer) {
if (this.selectedOrganizers.length >= 3) {
alert('You can only select up to 3 organizers.');
return;
}
if (this.selectedOrganizers.find(o => o.id === organizer.id)) {
return; // Already selected
}
this.selectedOrganizers.push(organizer);
this.renderSelectedOrganizers();
document.getElementById('organizer-search-results').style.display = 'none';
document.getElementById('organizer-search-input').value = '';
},
selectCategory: function(category) {
if (this.selectedCategories.length >= 3) {
alert('You can only select up to 3 categories.');
return;
}
if (this.selectedCategories.find(c => c.id === category.id)) {
return;
}
this.selectedCategories.push(category);
this.renderSelectedCategories();
document.getElementById('categories-search-results').style.display = 'none';
document.getElementById('categories-search-input').value = '';
},
selectVenue: function(venue) {
this.selectedVenue = venue;
document.getElementById('venue-search-input').value = venue.name;
document.getElementById('venue-search-results').style.display = 'none';
},
renderSelectedOrganizers: function() {
const container = document.getElementById('organizer-selected-items');
if (!container) return;
container.innerHTML = '';
this.selectedOrganizers.forEach((organizer, index) => {
const tag = document.createElement('div');
tag.className = 'selected-item';
tag.innerHTML = `
${organizer.name}
<button type="button" class="remove-item" onclick="HVACEventForm.removeOrganizer(${index})">
<span class="dashicons dashicons-no-alt"></span>
</button>
`;
container.appendChild(tag);
});
// Update hidden input
const hiddenInput = document.getElementById('event_organizer');
if (hiddenInput) {
hiddenInput.value = this.selectedOrganizers.map(o => o.id).join(',');
}
},
renderSelectedCategories: function() {
const container = document.getElementById('categories-selected-items');
if (!container) return;
container.innerHTML = '';
this.selectedCategories.forEach((category, index) => {
const tag = document.createElement('div');
tag.className = 'selected-item';
tag.innerHTML = `
${category.name}
<button type="button" class="remove-item" onclick="HVACEventForm.removeCategory(${index})">
<span class="dashicons dashicons-no-alt"></span>
</button>
`;
container.appendChild(tag);
});
const hiddenInput = document.getElementById('event_categories');
if (hiddenInput) {
hiddenInput.value = this.selectedCategories.map(c => c.id).join(',');
}
},
removeOrganizer: function(index) {
this.selectedOrganizers.splice(index, 1);
this.renderSelectedOrganizers();
},
removeCategory: function(index) {
this.selectedCategories.splice(index, 1);
this.renderSelectedCategories();
},
// Filter functions
filterOrganizerOptions: function(query) {
// In real implementation, this would make an AJAX call
this.loadOrganizerOptions();
},
filterCategoriesOptions: function(query) {
this.loadCategoriesOptions();
},
filterVenueOptions: function(query) {
this.loadVenueOptions();
},
// Modal Management
initModalForms: function() {
// Modal close handlers
$(document).on('click', '.hvac-modal-close, .hvac-modal-cancel', (e) => {
this.closeModal($(e.target).closest('.hvac-modal'));
});
// Save handlers
$(document).on('click', '#save-new-organizer', () => {
this.saveNewOrganizer();
});
$(document).on('click', '#save-new-category', () => {
this.saveNewCategory();
});
$(document).on('click', '#save-new-venue', () => {
this.saveNewVenue();
});
// Close on backdrop click
$(document).on('click', '.hvac-modal', function(e) {
if (e.target === this) {
HVACEventForm.closeModal($(this));
}
});
},
openModal: function(modalSelector) {
$(modalSelector).fadeIn(300);
// Focus first input
setTimeout(() => {
$(modalSelector).find('input:first').focus();
}, 300);
},
closeModal: function(modal) {
if (modal instanceof jQuery) {
modal.fadeOut(300);
// Clear form
modal.find('form')[0]?.reset();
} else {
$(modal).fadeOut(300);
$(modal).find('form')[0]?.reset();
}
},
saveNewOrganizer: function() {
const form = document.getElementById('new-organizer-form');
const formData = new FormData(form);
const name = formData.get('organizer_name');
if (!name.trim()) {
alert('Organizer name is required.');
return;
}
// Create new organizer object
const newOrganizer = {
id: Date.now(), // Temporary ID
name: name,
email: formData.get('organizer_email') || '',
organization: formData.get('organizer_organization') || ''
};
// Add to selection
this.selectOrganizer(newOrganizer);
this.closeModal('#new-organizer-modal');
},
saveNewCategory: function() {
const form = document.getElementById('new-category-form');
const formData = new FormData(form);
const name = formData.get('category_name');
if (!name.trim()) {
alert('Category name is required.');
return;
}
const newCategory = {
id: Date.now(),
name: name,
description: formData.get('category_description') || ''
};
this.selectCategory(newCategory);
this.closeModal('#new-category-modal');
},
saveNewVenue: function() {
const form = document.getElementById('new-venue-form');
const formData = new FormData(form);
const name = formData.get('venue_name');
const address = formData.get('venue_address');
if (!name.trim() || !address.trim()) {
alert('Venue name and address are required.');
return;
}
const newVenue = {
id: Date.now(),
name: name,
address: address
};
this.selectVenue(newVenue);
this.closeModal('#new-venue-modal');
},
// Event binding
bindEvents: function() {
// Form submission
$(document).on('submit', 'form', () => {
this.syncEditorContent();
});
// Auto-save functionality (if needed)
setInterval(() => {
this.syncEditorContent();
}, 5000);
}
};
// Initialize when DOM is ready
$(document).ready(function() {
HVACEventForm.init();
});
})(jQuery);