100) { add_settings_error('adpf_messages', 'adpf_error', __('Form ID is too long.', 'ad-pro-forms'), 'error'); return; } // Validate title length if (strlen($title) > 255) { add_settings_error('adpf_messages', 'adpf_error', __('Form title is too long.', 'ad-pro-forms'), 'error'); return; } if (empty($form_id) || empty($title)) { add_settings_error('adpf_messages', 'adpf_error', __('Form ID and Title are required.', 'ad-pro-forms'), 'error'); return; } // Validate JSON length to prevent DoS if (strlen($fields_json) > 100000) { // 100KB max add_settings_error('adpf_messages', 'adpf_error', __('Form configuration is too large.', 'ad-pro-forms'), 'error'); return; } // Decode and validate JSON $fields = json_decode($fields_json, true); // Check for JSON decode errors if (json_last_error() !== JSON_ERROR_NONE) { add_settings_error('adpf_messages', 'adpf_error', __('Invalid form configuration data.', 'ad-pro-forms'), 'error'); return; } if (!is_array($fields)) { $fields = array(); } // Validate and sanitize fields structure $validated_fields = array(); $max_fields = apply_filters('adpf_max_fields_per_form', 50); // Prevent DoS if (count($fields) > $max_fields) { /* translators: %d: maximum number of fields allowed */ add_settings_error('adpf_messages', 'adpf_error', sprintf(__('Too many fields. Maximum %d fields allowed.', 'ad-pro-forms'), $max_fields), 'error'); return; } foreach ($fields as $field) { if (!is_array($field)) { continue; // Skip invalid fields } // Validate required field properties if (!isset($field['type']) || !isset($field['name'])) { continue; // Skip fields without required properties } // Sanitize field data $field_width = isset($field['width']) ? intval($field['width']) : 100; if (!in_array($field_width, array(50, 100), true)) { $field_width = 100; } $validated_field = array( 'type' => sanitize_text_field($field['type']), 'name' => sanitize_text_field($field['name']), 'label' => isset($field['label']) ? sanitize_text_field($field['label']) : '', 'required' => isset($field['required']) ? (bool) $field['required'] : false, 'placeholder' => isset($field['placeholder']) ? sanitize_text_field($field['placeholder']) : '', 'explanation' => isset($field['explanation']) ? sanitize_text_field($field['explanation']) : '', 'width' => $field_width, ); // Validate field type (submit button is not allowed as a draggable field) $allowed_types = array('text', 'email', 'tel', 'textarea', 'number', 'radio', 'select', 'checkbox', 'turnstile', 'honeypot', 'html_text'); if (!in_array($validated_field['type'], $allowed_types, true)) { continue; // Skip invalid field types } // Remove submit buttons from fields (they're always at the bottom) if ($validated_field['type'] === 'submit') { continue; } // Validate field name (alphanumeric and underscore only) if (!preg_match('/^[a-z0-9_]+$/i', $validated_field['name'])) { continue; // Skip invalid field names } // Handle content for HTML text fields if ($validated_field['type'] === 'html_text' && isset($field['content'])) { $validated_field['content'] = wp_kses_post($field['content']); } // Handle options for radio/select (array of strings) if (in_array($validated_field['type'], array('radio', 'select'), true) && isset($field['options']) && is_array($field['options'])) { $validated_field['options'] = array_map('sanitize_text_field', array_slice($field['options'], 0, 50)); // Max 50 options } // Handle options for checkbox (array of objects with value and required) if ($validated_field['type'] === 'checkbox' && isset($field['options']) && is_array($field['options'])) { $checkbox_options = array(); foreach (array_slice($field['options'], 0, 50) as $option) { // Max 50 options if (is_array($option) && isset($option['value'])) { $checkbox_options[] = array( 'value' => sanitize_text_field($option['value']), 'required' => isset($option['required']) ? (bool) $option['required'] : false, ); } elseif (is_string($option)) { // Backward compatibility: if it's a string, convert to object $checkbox_options[] = array( 'value' => sanitize_text_field($option), 'required' => false, ); } } $validated_field['options'] = $checkbox_options; } $validated_fields[] = $validated_field; } $fields = $validated_fields; // No longer save styling - it's global now $config = array( 'fields' => $fields, ); // Add custom submit button text if provided if (!empty($submit_button_text)) { $config['submit_button_text'] = $submit_button_text; } $result = ADPF_Database::save_form($form_id, $title, $config); if ($result !== false) { add_settings_error('adpf_messages', 'adpf_message', __('Form saved successfully.', 'ad-pro-forms'), 'updated'); wp_safe_redirect(add_query_arg(array('page' => 'adpf-forms', 'action' => 'edit', 'form_id' => $form_id, 'saved' => '1'), admin_url('admin.php'))); exit; } else { add_settings_error('adpf_messages', 'adpf_error', __('Error saving form.', 'ad-pro-forms'), 'error'); } } /** * Handle form delete */ public function handle_form_delete() { if (!isset($_GET['adpf_delete_form']) || !isset($_GET['form_id'])) { return; } if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'ad-pro-forms')); } check_admin_referer('adpf_delete_form'); $form_id = sanitize_text_field(wp_unslash($_GET['form_id'])); ADPF_Database::delete_form($form_id); add_settings_error('adpf_messages', 'adpf_message', __('Form deleted successfully.', 'ad-pro-forms'), 'updated'); wp_safe_redirect(remove_query_arg(array('adpf_delete_form', 'form_id', '_wpnonce'), add_query_arg('page', 'adpf-forms', admin_url('admin.php')))); exit; } /** * Handle form duplicate */ public function handle_form_duplicate() { if (!isset($_GET['adpf_duplicate_form']) || !isset($_GET['form_id'])) { return; } if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'ad-pro-forms')); } check_admin_referer('adpf_duplicate_form'); wp_safe_redirect(add_query_arg('page', 'adpf-forms', admin_url('admin.php'))); exit; } /** * Render forms page */ public function render_forms_page() { if (!current_user_can('manage_options')) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Just determining which page to display $action = isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : 'list'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Just determining which page to display $form_id = isset($_GET['form_id']) ? sanitize_text_field(wp_unslash($_GET['form_id'])) : ''; settings_errors('adpf_messages'); if ($action === 'edit' || $action === 'new') { $this->render_form_editor($form_id, $action === 'new'); } else { $this->render_forms_list(); } } /** * Render forms list */ private function render_forms_list() { $forms = ADPF_Database::get_forms(); ?>