admin_url( 'admin-ajax.php' ), 'error_msg' => esc_html__( 'An error occurred while saving the form.', '24tt-unrestricted-forms' ), 'success_msg' => esc_html__( 'Form saved successfully!', '24tt-unrestricted-forms' ) ) ); } /** * Process the AJAX request to save a form. * This is the fortress. We validate, sanitize, and prepare everything before it touches the DB. * * @since 1.0.0 * @return void */ public function ajax_save_form() { global $wpdb; // WALL 1: Nonce Verification (Protects against CSRF attacks) // 'tt24_save_form_action' matches the nonce we created in view-builder.php if ( ! check_ajax_referer( 'tt24_save_form_action', 'security', false ) ) { wp_send_json_error( array( 'message' => esc_html__( 'Security check failed. Please refresh the page.', '24tt-unrestricted-forms' ) ) ); } // WALL 2: Capability Check (Role-Based Access Control) if ( ! current_user_can( 'manage_tt24_forms' ) ) { wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to build forms.', '24tt-unrestricted-forms' ) ) ); } // WALL 3: Data Sanitization // We strict-cast the ID to an absolute integer. If it's new, it will be 0. $form_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0; // Sanitize the title to remove any HTML or scripts $form_title = isset( $_POST['form_title'] ) ? sanitize_text_field( wp_unslash( $_POST['form_title'] ) ) : ''; if ( empty( $form_title ) ) { $form_title = esc_html__( 'Untitled Form', '24tt-unrestricted-forms' ); } // The layout data will come in as a JSON string representing the dragged-and-dropped fields. // BUG FIX: Securely decode and re-encode the JSON to prevent WordPress from stripping complex characters $raw_json_string = isset( $_POST['form_settings'] ) ? wp_unslash( $_POST['form_settings'] ) : '{}'; $decoded_json = json_decode( $raw_json_string, true ); $form_settings = $decoded_json ? wp_json_encode( $decoded_json ) : '{}'; $table_forms = $wpdb->prefix . 'tt24_forms'; // WALL 4: Database Interaction using $wpdb->prepare() if ( $form_id > 0 ) { // UPDATE EXISTING FORM // FIX: Silencing false-positive DB warnings // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching $updated = $wpdb->update( $table_forms, array( 'title' => $form_title, 'form_settings' => $form_settings, // updated_at is handled automatically by MySQL based on our schema ), array( 'id' => $form_id ), // WHERE clause array( '%s', '%s' ), // Data formats: string, string array( '%d' ) // WHERE format: integer ); // phpcs:enable if ( false === $updated ) { wp_send_json_error( array( 'message' => esc_html__( 'Failed to update the form in the database.', '24tt-unrestricted-forms' ) ) ); } wp_send_json_success( array( 'message' => esc_html__( 'Form updated successfully!', '24tt-unrestricted-forms' ), 'form_id' => $form_id ) ); } else { // INSERT NEW FORM // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching $inserted = $wpdb->insert( $table_forms, array( 'title' => $form_title, 'form_settings' => $form_settings, 'status' => 'publish' ), array( '%s', '%s', '%s' ) // Data formats: string, string, string ); // phpcs:enable if ( false === $inserted ) { wp_send_json_error( array( 'message' => esc_html__( 'Failed to create the new form.', '24tt-unrestricted-forms' ) ) ); } // Grab the newly generated ID $new_form_id = $wpdb->insert_id; wp_send_json_success( array( 'message' => esc_html__( 'Form created successfully!', '24tt-unrestricted-forms' ), 'form_id' => $new_form_id ) ); } // Always exit at the end of an AJAX request wp_die(); } } // Instantiate and initialize the class securely $tt24_form_builder = new TT24_Form_Builder(); $tt24_form_builder->init();