- 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>
781 lines
No EOL
26 KiB
PHP
781 lines
No EOL
26 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* HVAC Event Post Handler
|
|
*
|
|
* Handles native WordPress tribe_events post creation and editing
|
|
* Replaces TEC Community Events form submission with reliable WordPress-native approach
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @subpackage Includes
|
|
* @since 3.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Event_Post_Handler
|
|
*
|
|
* Manages tribe_events post creation, editing, and meta field mapping
|
|
*/
|
|
class HVAC_Event_Post_Handler {
|
|
|
|
use HVAC_Singleton_Trait;
|
|
|
|
/**
|
|
* TEC meta field mapping
|
|
* Maps form fields to tribe_events post meta keys
|
|
*
|
|
* @var array
|
|
*/
|
|
private array $tec_meta_mapping = [
|
|
'event_start_date' => '_EventStartDate',
|
|
'event_end_date' => '_EventEndDate',
|
|
'event_timezone' => '_EventTimezone',
|
|
'event_url' => '_EventURL',
|
|
'venue_name' => '_VenueName',
|
|
'venue_address' => '_VenueAddress',
|
|
'venue_city' => '_VenueCity',
|
|
'venue_state' => '_VenueState',
|
|
'venue_zip' => '_VenueZip',
|
|
'venue_capacity' => '_VenueCapacity',
|
|
'organizer_name' => '_OrganizerName',
|
|
'organizer_email' => '_OrganizerEmail',
|
|
'organizer_phone' => '_OrganizerPhone',
|
|
'organizer_website' => '_OrganizerWebsite',
|
|
];
|
|
|
|
/**
|
|
* HVAC-specific meta field mapping
|
|
*
|
|
* @var array
|
|
*/
|
|
private array $hvac_meta_mapping = [
|
|
'trainer_requirements' => '_hvac_trainer_requirements',
|
|
'certification_levels' => '_hvac_certification_levels',
|
|
'equipment_needed' => '_hvac_equipment_needed',
|
|
'prerequisites' => '_hvac_prerequisites',
|
|
];
|
|
|
|
/**
|
|
* Required TEC meta fields with default values
|
|
*
|
|
* @var array
|
|
*/
|
|
private array $required_tec_meta = [
|
|
'_EventOrigin' => 'hvac-community-events',
|
|
'_EventShowMap' => '1',
|
|
'_EventShowMapLink' => '1',
|
|
'_tribe_default_ticket_provider' => 'TEC\\Tickets\\Commerce\\Module',
|
|
'_tribe_ticket_capacity' => '0',
|
|
'_tribe_ticket_version' => '5.25.1.1',
|
|
];
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Initialize WordPress hooks
|
|
*/
|
|
private function init_hooks(): void {
|
|
// Handle form submissions
|
|
add_action('template_redirect', [$this, 'handle_event_form_submission']);
|
|
|
|
// Handle AJAX submissions (for future enhancement)
|
|
add_action('wp_ajax_hvac_create_event', [$this, 'ajax_create_event']);
|
|
add_action('wp_ajax_hvac_update_event', [$this, 'ajax_update_event']);
|
|
}
|
|
|
|
/**
|
|
* Handle event form submission
|
|
*/
|
|
public function handle_event_form_submission(): void {
|
|
// Only process POST requests with our nonce
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['hvac_event_form_nonce'])) {
|
|
return;
|
|
}
|
|
|
|
// Verify nonce for security
|
|
if (!HVAC_Security::verify_nonce($_POST['hvac_event_form_nonce'], 'hvac_event_form')) {
|
|
wp_die(__('Security check failed. Please refresh the page and try again.', 'hvac-community-events'));
|
|
return;
|
|
}
|
|
|
|
// Check user permissions
|
|
if (!HVAC_Security::check_capability('edit_posts')) {
|
|
wp_die(__('You do not have permission to create events.', 'hvac-community-events'));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create form builder instance for validation
|
|
$event_form = new HVAC_Event_Form_Builder('hvac_event_form');
|
|
$event_form->create_event_form();
|
|
|
|
// Validate submitted data
|
|
$validation_errors = $event_form->validate($_POST);
|
|
|
|
if (!empty($validation_errors)) {
|
|
// Store errors in session for display
|
|
$this->store_form_errors($validation_errors);
|
|
$this->store_form_data($_POST);
|
|
return;
|
|
}
|
|
|
|
// Sanitize data
|
|
$sanitized_data = $event_form->sanitize($_POST);
|
|
|
|
// Determine if this is create or update
|
|
$event_id = isset($sanitized_data['event_id']) ? absint($sanitized_data['event_id']) : 0;
|
|
|
|
if ($event_id > 0) {
|
|
// Update existing event
|
|
$result = $this->update_event($event_id, $sanitized_data);
|
|
} else {
|
|
// Create new event
|
|
$result = $this->create_event($sanitized_data);
|
|
}
|
|
|
|
if (is_wp_error($result)) {
|
|
$this->store_form_errors(['general' => $result->get_error_message()]);
|
|
$this->store_form_data($_POST);
|
|
return;
|
|
}
|
|
|
|
// Success - redirect to prevent resubmission
|
|
$redirect_url = add_query_arg(['event_created' => '1', 'event_id' => $result], wp_get_referer());
|
|
wp_safe_redirect($redirect_url);
|
|
exit;
|
|
|
|
} catch (Exception $e) {
|
|
HVAC_Logger::error('Event form submission failed', 'Event_Post_Handler', [
|
|
'error' => $e->getMessage(),
|
|
'user_id' => get_current_user_id(),
|
|
'post_data' => wp_json_encode($_POST)
|
|
]);
|
|
|
|
$this->store_form_errors(['general' => 'An error occurred while processing your event. Please try again.']);
|
|
$this->store_form_data($_POST);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create new tribe_events post
|
|
*
|
|
* @param array $data Sanitized form data
|
|
* @return int|WP_Error Post ID on success, WP_Error on failure
|
|
*/
|
|
public function create_event(array $data) {
|
|
// Prepare post data with security sanitization
|
|
$post_data = [
|
|
'post_type' => 'tribe_events',
|
|
'post_title' => sanitize_text_field($data['event_title'] ?? ''),
|
|
'post_content' => $this->sanitize_rich_text_content($data['event_description'] ?? ''),
|
|
'post_excerpt' => sanitize_textarea_field($data['event_excerpt'] ?? ''),
|
|
'post_status' => $this->get_post_status_for_user(),
|
|
'post_author' => get_current_user_id(),
|
|
'meta_input' => $this->prepare_meta_fields($data),
|
|
];
|
|
|
|
// Create the post
|
|
$event_id = wp_insert_post($post_data, true);
|
|
|
|
if (is_wp_error($event_id)) {
|
|
return $event_id;
|
|
}
|
|
|
|
// Handle featured image upload
|
|
$this->handle_featured_image_upload($event_id, $data);
|
|
|
|
// Handle venue creation/assignment
|
|
$venue_id = $this->handle_venue_data($data);
|
|
if ($venue_id) {
|
|
update_post_meta($event_id, '_EventVenueID', $venue_id);
|
|
}
|
|
|
|
// Handle organizer creation/assignment
|
|
$organizer_id = $this->handle_organizer_data($data);
|
|
if ($organizer_id) {
|
|
update_post_meta($event_id, '_EventOrganizerID', $organizer_id);
|
|
}
|
|
|
|
// Calculate and store additional TEC meta fields
|
|
$this->calculate_tec_meta_fields($event_id, $data);
|
|
|
|
// Log successful creation
|
|
HVAC_Logger::info('Event created successfully', 'Event_Post_Handler', [
|
|
'event_id' => $event_id,
|
|
'user_id' => get_current_user_id(),
|
|
'event_title' => $data['event_title'] ?? 'Unknown'
|
|
]);
|
|
|
|
return $event_id;
|
|
}
|
|
|
|
/**
|
|
* Update existing tribe_events post
|
|
*
|
|
* @param int $event_id Event post ID
|
|
* @param array $data Sanitized form data
|
|
* @return int|WP_Error Post ID on success, WP_Error on failure
|
|
*/
|
|
public function update_event(int $event_id, array $data) {
|
|
// Verify user can edit this event
|
|
if (!current_user_can('edit_post', $event_id)) {
|
|
return new WP_Error('permission_denied', 'You do not have permission to edit this event.');
|
|
}
|
|
|
|
// Prepare post data with security sanitization
|
|
$post_data = [
|
|
'ID' => $event_id,
|
|
'post_title' => sanitize_text_field($data['event_title'] ?? ''),
|
|
'post_content' => $this->sanitize_rich_text_content($data['event_description'] ?? ''),
|
|
'post_excerpt' => $data['event_excerpt'] ?? '',
|
|
];
|
|
|
|
// Update the post
|
|
$result = wp_update_post($post_data, true);
|
|
|
|
if (is_wp_error($result)) {
|
|
return $result;
|
|
}
|
|
|
|
// Update meta fields
|
|
$meta_fields = $this->prepare_meta_fields($data);
|
|
foreach ($meta_fields as $key => $value) {
|
|
update_post_meta($event_id, $key, $value);
|
|
}
|
|
|
|
// Handle featured image upload
|
|
$this->handle_featured_image_upload($event_id, $data);
|
|
|
|
// Update venue
|
|
$venue_id = $this->handle_venue_data($data);
|
|
if ($venue_id) {
|
|
update_post_meta($event_id, '_EventVenueID', $venue_id);
|
|
}
|
|
|
|
// Update organizer
|
|
$organizer_id = $this->handle_organizer_data($data);
|
|
if ($organizer_id) {
|
|
update_post_meta($event_id, '_EventOrganizerID', $organizer_id);
|
|
}
|
|
|
|
// Recalculate TEC meta fields
|
|
$this->calculate_tec_meta_fields($event_id, $data);
|
|
|
|
// Log successful update
|
|
HVAC_Logger::info('Event updated successfully', 'Event_Post_Handler', [
|
|
'event_id' => $event_id,
|
|
'user_id' => get_current_user_id(),
|
|
'event_title' => $data['event_title'] ?? 'Unknown'
|
|
]);
|
|
|
|
return $event_id;
|
|
}
|
|
|
|
/**
|
|
* Prepare meta fields for post creation/update
|
|
*
|
|
* @param array $data Sanitized form data
|
|
* @return array Meta fields array
|
|
*/
|
|
private function prepare_meta_fields(array $data): array {
|
|
$meta_fields = [];
|
|
|
|
// Map TEC meta fields
|
|
foreach ($this->tec_meta_mapping as $form_field => $meta_key) {
|
|
if (isset($data[$form_field]) && $data[$form_field] !== '') {
|
|
$meta_fields[$meta_key] = $data[$form_field];
|
|
}
|
|
}
|
|
|
|
// Map HVAC-specific meta fields
|
|
foreach ($this->hvac_meta_mapping as $form_field => $meta_key) {
|
|
if (isset($data[$form_field]) && $data[$form_field] !== '') {
|
|
$meta_fields[$meta_key] = $data[$form_field];
|
|
}
|
|
}
|
|
|
|
// Add required TEC meta fields with defaults
|
|
$meta_fields = array_merge($meta_fields, $this->required_tec_meta);
|
|
|
|
// Handle special datetime processing
|
|
if (isset($data['event_start_date'])) {
|
|
$meta_fields['_EventStartDate'] = $this->format_datetime_for_tec($data['event_start_date'], $data['event_timezone'] ?? '');
|
|
$meta_fields['_EventStartDateUTC'] = $this->convert_to_utc($data['event_start_date'], $data['event_timezone'] ?? '');
|
|
}
|
|
|
|
if (isset($data['event_end_date'])) {
|
|
$meta_fields['_EventEndDate'] = $this->format_datetime_for_tec($data['event_end_date'], $data['event_timezone'] ?? '');
|
|
$meta_fields['_EventEndDateUTC'] = $this->convert_to_utc($data['event_end_date'], $data['event_timezone'] ?? '');
|
|
}
|
|
|
|
// Handle timezone
|
|
if (isset($data['event_timezone'])) {
|
|
$meta_fields['_EventTimezone'] = $data['event_timezone'];
|
|
$meta_fields['_EventTimezoneAbbr'] = $this->get_timezone_abbreviation($data['event_timezone']);
|
|
}
|
|
|
|
return $meta_fields;
|
|
}
|
|
|
|
/**
|
|
* Calculate and store additional TEC meta fields
|
|
*
|
|
* @param int $event_id Event post ID
|
|
* @param array $data Sanitized form data
|
|
*/
|
|
private function calculate_tec_meta_fields(int $event_id, array $data): void {
|
|
// Calculate event duration
|
|
if (isset($data['event_start_date']) && isset($data['event_end_date'])) {
|
|
$start_time = strtotime($data['event_start_date']);
|
|
$end_time = strtotime($data['event_end_date']);
|
|
$duration = $end_time - $start_time;
|
|
update_post_meta($event_id, '_EventDuration', $duration);
|
|
}
|
|
|
|
// Update modified fields timestamp
|
|
update_post_meta($event_id, '_tribe_modified_fields', [
|
|
'_EventStartDate' => time(),
|
|
'_EventEndDate' => time(),
|
|
'_EventVenueID' => time(),
|
|
'_EventOrganizerID' => time(),
|
|
'_EventURL' => time(),
|
|
'_EventTimezone' => time(),
|
|
'_EventOrigin' => time(),
|
|
]);
|
|
|
|
// Mark as non-duplicated event
|
|
update_post_meta($event_id, '_EventOccurrencesCount', 1);
|
|
update_post_meta($event_id, '_edit_last', get_current_user_id());
|
|
}
|
|
|
|
/**
|
|
* Handle venue data - create or update venue post
|
|
*
|
|
* @param array $data Sanitized form data
|
|
* @return int|null Venue post ID or null
|
|
*/
|
|
private function handle_venue_data(array $data): ?int {
|
|
if (empty($data['venue_name'])) {
|
|
return null;
|
|
}
|
|
|
|
// Check if venue already exists
|
|
$existing_venue = get_posts([
|
|
'post_type' => 'tribe_venue',
|
|
'post_title' => $data['venue_name'],
|
|
'posts_per_page' => 1,
|
|
'post_status' => 'publish'
|
|
]);
|
|
|
|
if (!empty($existing_venue)) {
|
|
return $existing_venue[0]->ID;
|
|
}
|
|
|
|
// Create new venue
|
|
$venue_data = [
|
|
'post_type' => 'tribe_venue',
|
|
'post_title' => $data['venue_name'],
|
|
'post_status' => 'publish',
|
|
'meta_input' => [
|
|
'_VenueAddress' => $data['venue_address'] ?? '',
|
|
'_VenueCity' => $data['venue_city'] ?? '',
|
|
'_VenueState' => $data['venue_state'] ?? '',
|
|
'_VenueZip' => $data['venue_zip'] ?? '',
|
|
'_VenueCapacity' => $data['venue_capacity'] ?? '',
|
|
]
|
|
];
|
|
|
|
// Add coordinates if available
|
|
if (!empty($data['venue_latitude']) && !empty($data['venue_longitude'])) {
|
|
$venue_data['meta_input']['_VenueLat'] = $data['venue_latitude'];
|
|
$venue_data['meta_input']['_VenueLng'] = $data['venue_longitude'];
|
|
}
|
|
|
|
$venue_id = wp_insert_post($venue_data);
|
|
return is_wp_error($venue_id) ? null : $venue_id;
|
|
}
|
|
|
|
/**
|
|
* Handle organizer data - create or update organizer post
|
|
*
|
|
* @param array $data Sanitized form data
|
|
* @return int|null Organizer post ID or null
|
|
*/
|
|
private function handle_organizer_data(array $data): ?int {
|
|
if (empty($data['organizer_name'])) {
|
|
return null;
|
|
}
|
|
|
|
// Check if organizer already exists
|
|
$existing_organizer = get_posts([
|
|
'post_type' => 'tribe_organizer',
|
|
'post_title' => $data['organizer_name'],
|
|
'posts_per_page' => 1,
|
|
'post_status' => 'publish'
|
|
]);
|
|
|
|
if (!empty($existing_organizer)) {
|
|
return $existing_organizer[0]->ID;
|
|
}
|
|
|
|
// Create new organizer
|
|
$organizer_data = [
|
|
'post_type' => 'tribe_organizer',
|
|
'post_title' => $data['organizer_name'],
|
|
'post_status' => 'publish',
|
|
'meta_input' => [
|
|
'_OrganizerEmail' => $data['organizer_email'] ?? '',
|
|
'_OrganizerPhone' => $data['organizer_phone'] ?? '',
|
|
'_OrganizerWebsite' => $data['organizer_website'] ?? '',
|
|
]
|
|
];
|
|
|
|
$organizer_id = wp_insert_post($organizer_data);
|
|
return is_wp_error($organizer_id) ? null : $organizer_id;
|
|
}
|
|
|
|
/**
|
|
* Handle featured image upload
|
|
*
|
|
* @param int $event_id Event post ID
|
|
* @param array $data Form data
|
|
*/
|
|
private function handle_featured_image_upload(int $event_id, array $data): void {
|
|
if (empty($_FILES['event_featured_image']['name'])) {
|
|
return;
|
|
}
|
|
|
|
// WordPress file upload handling
|
|
require_once(ABSPATH . 'wp-admin/includes/file.php');
|
|
require_once(ABSPATH . 'wp-admin/includes/image.php');
|
|
require_once(ABSPATH . 'wp-admin/includes/media.php');
|
|
|
|
$file = $_FILES['event_featured_image'];
|
|
|
|
// Enhanced security validation for file uploads
|
|
$validation_result = $this->validate_file_upload($file);
|
|
if (is_wp_error($validation_result)) {
|
|
// Log security violation
|
|
error_log(sprintf(
|
|
'[HVAC Security] File upload blocked: %s (User: %d, File: %s)',
|
|
$validation_result->get_error_message(),
|
|
get_current_user_id(),
|
|
$file['name']
|
|
));
|
|
return;
|
|
}
|
|
|
|
// Handle upload
|
|
$attachment_id = media_handle_upload('event_featured_image', $event_id);
|
|
|
|
if (!is_wp_error($attachment_id)) {
|
|
set_post_thumbnail($event_id, $attachment_id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get appropriate post status for current user
|
|
*
|
|
* @return string Post status
|
|
*/
|
|
private function get_post_status_for_user(): string {
|
|
$user = wp_get_current_user();
|
|
|
|
// Master trainers can publish directly
|
|
if (in_array('hvac_master_trainer', $user->roles)) {
|
|
return 'publish';
|
|
}
|
|
|
|
// Regular trainers create drafts for approval
|
|
if (in_array('hvac_trainer', $user->roles)) {
|
|
return 'draft';
|
|
}
|
|
|
|
// Administrators can publish
|
|
if (in_array('administrator', $user->roles)) {
|
|
return 'publish';
|
|
}
|
|
|
|
// Default to draft
|
|
return 'draft';
|
|
}
|
|
|
|
/**
|
|
* Format datetime for TEC compatibility
|
|
*
|
|
* @param string $datetime Datetime string
|
|
* @param string $timezone Timezone string
|
|
* @return string Formatted datetime
|
|
*/
|
|
private function format_datetime_for_tec(string $datetime, string $timezone): string {
|
|
if (empty($datetime)) {
|
|
return '';
|
|
}
|
|
|
|
// TEC expects 'Y-m-d H:i:s' format
|
|
$timestamp = strtotime($datetime);
|
|
return date('Y-m-d H:i:s', $timestamp);
|
|
}
|
|
|
|
/**
|
|
* Convert datetime to UTC for TEC
|
|
*
|
|
* @param string $datetime Datetime string
|
|
* @param string $timezone Timezone string
|
|
* @return string UTC datetime
|
|
*/
|
|
private function convert_to_utc(string $datetime, string $timezone): string {
|
|
if (empty($datetime) || empty($timezone)) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
$dt = new DateTime($datetime, new DateTimeZone($timezone));
|
|
$dt->setTimezone(new DateTimeZone('UTC'));
|
|
return $dt->format('Y-m-d H:i:s');
|
|
} catch (Exception $e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get timezone abbreviation
|
|
*
|
|
* @param string $timezone Timezone string
|
|
* @return string Timezone abbreviation
|
|
*/
|
|
private function get_timezone_abbreviation(string $timezone): string {
|
|
try {
|
|
$dt = new DateTime('now', new DateTimeZone($timezone));
|
|
return $dt->format('T');
|
|
} catch (Exception $e) {
|
|
return 'UTC';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store form errors in session/transient
|
|
*
|
|
* @param array $errors Form errors
|
|
*/
|
|
private function store_form_errors(array $errors): void {
|
|
set_transient('hvac_form_errors_' . get_current_user_id(), $errors, 300); // 5 minutes
|
|
}
|
|
|
|
/**
|
|
* Store form data in session/transient for re-population
|
|
*
|
|
* @param array $data Form data
|
|
*/
|
|
private function store_form_data(array $data): void {
|
|
// Remove sensitive data before storing
|
|
unset($data['hvac_event_form_nonce']);
|
|
set_transient('hvac_form_data_' . get_current_user_id(), $data, 300); // 5 minutes
|
|
}
|
|
|
|
/**
|
|
* Get stored form errors
|
|
*
|
|
* @return array Form errors
|
|
*/
|
|
public function get_stored_errors(): array {
|
|
$errors = get_transient('hvac_form_errors_' . get_current_user_id());
|
|
delete_transient('hvac_form_errors_' . get_current_user_id());
|
|
return is_array($errors) ? $errors : [];
|
|
}
|
|
|
|
/**
|
|
* Get stored form data
|
|
*
|
|
* @return array Form data
|
|
*/
|
|
public function get_stored_data(): array {
|
|
$data = get_transient('hvac_form_data_' . get_current_user_id());
|
|
delete_transient('hvac_form_data_' . get_current_user_id());
|
|
return is_array($data) ? $data : [];
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for event creation
|
|
*/
|
|
public function ajax_create_event(): void {
|
|
// Verify nonce
|
|
if (!HVAC_Security::verify_nonce($_POST['nonce'] ?? '', 'hvac_create_event')) {
|
|
wp_send_json_error(['message' => 'Security check failed']);
|
|
return;
|
|
}
|
|
|
|
// Check permissions
|
|
if (!HVAC_Security::check_capability('edit_posts')) {
|
|
wp_send_json_error(['message' => 'Permission denied']);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$result = $this->create_event($_POST);
|
|
|
|
if (is_wp_error($result)) {
|
|
wp_send_json_error(['message' => $result->get_error_message()]);
|
|
return;
|
|
}
|
|
|
|
wp_send_json_success([
|
|
'message' => 'Event created successfully',
|
|
'event_id' => $result,
|
|
'edit_url' => get_edit_post_link($result)
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
wp_send_json_error(['message' => 'An error occurred while creating the event']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for event updates
|
|
*/
|
|
public function ajax_update_event(): void {
|
|
// Verify nonce
|
|
if (!HVAC_Security::verify_nonce($_POST['nonce'] ?? '', 'hvac_update_event')) {
|
|
wp_send_json_error(['message' => 'Security check failed']);
|
|
return;
|
|
}
|
|
|
|
$event_id = absint($_POST['event_id'] ?? 0);
|
|
if (!$event_id) {
|
|
wp_send_json_error(['message' => 'Invalid event ID']);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$result = $this->update_event($event_id, $_POST);
|
|
|
|
if (is_wp_error($result)) {
|
|
wp_send_json_error(['message' => $result->get_error_message()]);
|
|
return;
|
|
}
|
|
|
|
wp_send_json_success([
|
|
'message' => 'Event updated successfully',
|
|
'event_id' => $result,
|
|
'edit_url' => get_edit_post_link($result)
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
wp_send_json_error(['message' => 'An error occurred while updating the event']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sanitize rich text content to prevent XSS attacks
|
|
*
|
|
* @param string $content Raw HTML content from rich text editor
|
|
* @return string Sanitized HTML content
|
|
*/
|
|
private function sanitize_rich_text_content(string $content): string {
|
|
// Define allowed HTML tags and attributes for event descriptions
|
|
$allowed_html = array(
|
|
'p' => array(),
|
|
'br' => array(),
|
|
'strong' => array(),
|
|
'em' => array(),
|
|
'ul' => array(),
|
|
'ol' => array(),
|
|
'li' => array(),
|
|
'a' => array(
|
|
'href' => array(),
|
|
'title' => array(),
|
|
'target' => array()
|
|
),
|
|
'h3' => array(),
|
|
'h4' => array(),
|
|
'h5' => array(),
|
|
'blockquote' => array()
|
|
);
|
|
|
|
// Use WordPress wp_kses for server-side sanitization
|
|
return wp_kses($content, $allowed_html);
|
|
}
|
|
|
|
/**
|
|
* Validate file upload for security
|
|
*
|
|
* @param array $file WordPress $_FILES array element
|
|
* @return bool|WP_Error True on success, WP_Error on failure
|
|
*/
|
|
private function validate_file_upload(array $file) {
|
|
// MIME type whitelist - images and PDFs only
|
|
$allowed_types = array(
|
|
'image/jpeg',
|
|
'image/png',
|
|
'image/gif',
|
|
'image/webp'
|
|
);
|
|
|
|
// File extension whitelist
|
|
$allowed_extensions = array('jpg', 'jpeg', 'png', 'gif', 'webp');
|
|
|
|
// Check for upload errors
|
|
if ($file['error'] !== UPLOAD_ERR_OK) {
|
|
return new WP_Error('upload_error',
|
|
__('File upload failed with error code: ' . $file['error'], 'hvac-community-events'));
|
|
}
|
|
|
|
// Validate MIME type
|
|
if (!in_array($file['type'], $allowed_types)) {
|
|
return new WP_Error('invalid_file_type',
|
|
__('File type not allowed. Only JPEG, PNG, GIF, and WebP images are permitted.', 'hvac-community-events'));
|
|
}
|
|
|
|
// Validate file extension (double-check against spoofed MIME types)
|
|
$file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
|
if (!in_array($file_extension, $allowed_extensions)) {
|
|
return new WP_Error('invalid_file_extension',
|
|
__('File extension not allowed.', 'hvac-community-events'));
|
|
}
|
|
|
|
// File size limit (5MB maximum)
|
|
$max_size = 5 * 1024 * 1024; // 5MB in bytes
|
|
if ($file['size'] > $max_size) {
|
|
return new WP_Error('file_too_large',
|
|
__('File size exceeds 5MB limit. Please choose a smaller image.', 'hvac-community-events'));
|
|
}
|
|
|
|
// Check for malicious file content using getimagesize for images
|
|
if (in_array($file_extension, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
|
|
$image_info = @getimagesize($file['tmp_name']);
|
|
if ($image_info === false) {
|
|
return new WP_Error('invalid_image',
|
|
__('File appears to be corrupted or is not a valid image.', 'hvac-community-events'));
|
|
}
|
|
|
|
// Verify MIME type matches actual image type
|
|
$actual_mime = $image_info['mime'];
|
|
if ($actual_mime !== $file['type']) {
|
|
return new WP_Error('mime_mismatch',
|
|
__('File type mismatch detected. Upload blocked for security.', 'hvac-community-events'));
|
|
}
|
|
}
|
|
|
|
// Additional security: Check for embedded PHP or script content in image files
|
|
$file_content = file_get_contents($file['tmp_name'], false, null, 0, 1024); // Read first 1KB
|
|
if (stripos($file_content, '<?php') !== false ||
|
|
stripos($file_content, '<script') !== false ||
|
|
stripos($file_content, '<%') !== false) {
|
|
return new WP_Error('malicious_content',
|
|
__('File contains potentially malicious content and has been blocked.', 'hvac-community-events'));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} |