Skip to content

Conversation

@arifulhoque7
Copy link
Contributor

@arifulhoque7 arifulhoque7 commented Nov 25, 2025

feature/ai-field-options-generator Close issue

Add AI-Powered Field Options Generator for Dropdown, Radio, and Checkbox Fields

This branch introduces an AI-powered option generator within the form builder, allowing users to instantly generate field options by describing what they need (e.g., “List of US states”, “Startup categories”, “Common food allergens”). The AI returns a clean, structured list that can be reviewed, selected, and imported into the field with one click.


Key Changes

Backend (PHP)

  • Added new AJAX endpoint: wpuf_ai_generate_field_options in RestController.php
  • Integrated option generation with OpenAI, Anthropic, and Google AI providers via FormGenerator.php
  • Included AI initialization flags in form builder localized data (Admin_Form_Builder.php)
  • Added internal AI configuration validation without depending on external helper functions
  • Standardized JSON response parsing for option lists

Frontend (Vue 2)

  • Added a dedicated AI Options Modal to the field-option-data component

    • Prompt input
    • Generated preview list
    • Select All / Deselect All
    • Import Selected → instantly populates field options
  • Implemented proper Vue 2 reactivity using $set() to update dynamic lists

  • Added fallback modal for AI Provider Not Configured

  • Improved AJAX handling (loading, success, error states)


UI / UX Enhancements

  • Added compact AI button (8×8px) with purple→blue gradient beside Label & Values
  • Added 14×14px white-stroke sparkles icon matching new AI branding
  • Styled modal using builder_class_names() for consistent WP User Frontend design
  • Gradient-styled action buttons (Generate, Import Selected)
  • Added loading spinner during generation
  • Clear error messaging for invalid prompt, failed request, or empty response

Asset Build & Packaging

  • Updated Grunt config to compile and concatenate JS + PHP templates for new modal
  • All assets built and production-ready
  • No breaking changes to existing form fields or builder components

This feature delivers a smooth, modern AI workflow inside the WP User Frontend form builder, reducing manual effort and improving usability—especially for fields requiring long or complex option lists.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added AI-assisted option generation for form fields with an intuitive modal interface
    • Users can now generate field options using AI prompts with support for multiple AI providers
    • New AI provider configuration modal to set up and manage AI provider settings
    • Options generated by AI can be selected and imported into form fields
    • Loading states and error handling for seamless AI integration experience

✏️ Tip: You can customize this high-level summary in your review settings.

@arifulhoque7 arifulhoque7 requested a review from sapayth November 25, 2025 06:44
@arifulhoque7 arifulhoque7 self-assigned this Nov 25, 2025
@arifulhoque7 arifulhoque7 added needs: testing needs: dev review This PR needs review by a developer labels Nov 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

This PR introduces AI-assisted option generation for form fields, adding frontend modals and state management, backend API endpoints, AI provider integrations (OpenAI, Anthropic, Google), and corresponding CSS styling to enable users to generate form field options via AI through the form builder interface.

Changes

Cohort / File(s) Summary
Frontend Field Option Data Component
admin/form-builder/assets/js/components/field-option-data/index.js, assets/js/wpuf-form-builder-components.js
Adds AI workflow state (show_ai_modal, show_ai_config_modal, ai_prompt, ai_loading, ai_error, ai_generated_options), computed property all_ai_selected, and seven new methods (open_ai_modal, close_ai_config_modal, go_to_ai_settings, close_ai_modal, generate_ai_options, select_all_ai_options, import_ai_options) to manage AI modal display, option generation via AJAX, selection toggling, and option import.
Frontend Templates
admin/form-builder/assets/js/components/field-option-data/template.php, assets/js-templates/form-components.php
Adds AI-generate-options button next to Label & Values label, introduces two AI modals (option generation with prompt/error/options list, provider configuration), includes conditional rendering, accessibility scaffolding, and localized text.
Backend API Integration
includes/AI/RestController.php
Registers new REST route /wpuf/v1/ai-form-builder/generate-options and AJAX action wp_ajax_wpuf_ai_generate_field_options; implements generate_field_options (REST handler), ajax_generate_field_options (AJAX handler), and sanitize_field_options private method with validation and error handling.
Backend AI Logic
includes/AI/FormGenerator.php
Introduces public generate_field_options method and private helpers (get_field_options_system_prompt, call_openai_for_options, call_anthropic_for_options, call_google_for_options, parse_options_response) to orchestrate AI provider calls, handle multiple AI providers, normalize responses, and return structured results with success/options/provider/model.
Admin Configuration
includes/Admin/Forms/Admin_Form_Builder.php
Adds AI configuration check (ai_configured, ai_settings_url) and exposes these to frontend via localized script for conditional UI behavior.
Styling
src/css/admin/form-builder.css
Adds CSS utilities for AI modal overlay, container, header, close button, body, footer, and options list with fixed positioning, sizing, and scrolling constraints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Backend provider integrations: Multiple AI provider handlers (OpenAI, Anthropic, Google) with distinct API patterns, request/response normalization, and error handling logic require careful validation
  • State management: Seven new methods managing AI workflow across multiple state flags; verify modal lifecycle and option selection logic
  • AJAX/REST integration: Dual-path implementation (legacy AJAX + modern REST) with validation/sanitization; ensure consistency between endpoints
  • Frontend-backend contract: Verify data mapping between generated options and UI state, particularly in generate_ai_options AJAX callback and import_ai_options logic
  • CSS utility consistency: Ensure modal styling aligns with existing form-builder design patterns

Possibly related issues

  • weDevsOfficial/wpuf-pro#1244: Implements identical AI "Generate Options" workflow across field-option-data component, form-builder templates, and server-side AJAX/REST endpoints with FormGenerator provider support.

Possibly related PRs

Suggested reviewers

  • sapayth

Poem

🐰 Hops through code with AI's might,
Generating options left and right,
From OpenAI to Google's call,
Form builders now have it all!
Modal windows dance with flair,
Magic options float through air!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main feature added: an AI-powered field options generator for dropdown, radio, and checkbox fields. It is clear, specific, and directly corresponds to the primary objective and changes in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
assets/js-templates/form-components.php (1)

413-427: AI options UI is well-integrated; consider de-duplicating modal markup

The AI trigger button and both modals are wired correctly into the existing option-data template (state flags, handlers, and Tailwind utility classes all line up with the new CSS and backend endpoints).

The same AI header + modal blocks now exist here and in admin/form-builder/assets/js/components/field-option-data/template.php, which will be easy to accidentally diverge over time. If you keep iterating on this feature, consider extracting the shared modal markup into a single PHP partial or Vue sub-template and including it from both places to reduce duplication.

Also applies to: 510-603

admin/form-builder/assets/js/components/field-option-data/template.php (1)

28-42: Keep this AI modal template in sync with the global form-components template

This component template’s AI button and modals are consistent with the markup added to assets/js-templates/form-components.php and should work fine with the shared JS logic and CSS.

Because the two files now contain nearly identical AI sections, any future copy or behavior tweak will have to be applied in both places. If this pattern solidifies, consider extracting the AI options modal into a shared partial or Vue sub-template and including it here and in form-components.php to avoid drift.

Also applies to: 125-218

assets/js/wpuf-form-builder-components.js (1)

816-821: Vue 2 reactivity issue when toggling selection.

Directly mutating opt.selected on array items may not trigger reactivity in Vue 2. Consider using this.$set() for each item to ensure the UI updates correctly.

Apply this diff:

         select_all_ai_options: function () {
             var select_state = !this.all_ai_selected;
+            var self = this;
-            this.ai_generated_options.forEach(function(opt) {
-                opt.selected = select_state;
+            this.ai_generated_options.forEach(function(opt, index) {
+                self.$set(self.ai_generated_options[index], 'selected', select_state);
             });
         },
admin/form-builder/assets/js/components/field-option-data/index.js (1)

182-187: Same Vue 2 reactivity concern as in bundled file.

Direct property mutation on array items may not trigger Vue 2 reactivity. Use this.$set() for reliable updates.

Apply this diff:

         select_all_ai_options: function () {
             var select_state = !this.all_ai_selected;
+            var self = this;
-            this.ai_generated_options.forEach(function(opt) {
-                opt.selected = select_state;
+            this.ai_generated_options.forEach(function(opt, index) {
+                self.$set(self.ai_generated_options[index], 'selected', select_state);
             });
         },
includes/AI/FormGenerator.php (2)

1258-1268: Remove unused $index variable.

The static analysis correctly identified that $index is unused in the foreach loop.

Apply this diff:

             } else {
                 // Expecting array of strings (one per line)
-                foreach ($parsed['options'] as $index => $option) {
+                foreach ($parsed['options'] as $option) {
                     if (is_string($option)) {
                         $field_options[] = [
                             'label' => $option,
                             'value' => sanitize_title( $option )
                         ];
                     }
                 }
             }

1019-1074: Consider using model-specific token parameter.

The method hardcodes max_tokens to 1000, but get_model_config is called without using its token_param configuration. For consistency with generate_form, consider using the model config.

         $body = [
             'model' => $this->current_model,
             'messages' => [
                 ['role' => 'system', 'content' => $system_prompt],
                 ['role' => 'user', 'content' => $user_prompt]
             ],
-            'temperature' => 0.7,
-            'max_tokens' => 1000
+            'temperature' => 0.7
         ];

+        // Set token parameter based on model
+        if ($model_config['token_location'] === 'body') {
+            $body[$model_config['token_param']] = 1000;
+        }
+
         if ($model_config['supports_json_mode']) {
             $body['response_format'] = ['type' => 'json_object'];
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47b6597 and 13a9116.

📒 Files selected for processing (8)
  • admin/form-builder/assets/js/components/field-option-data/index.js (3 hunks)
  • admin/form-builder/assets/js/components/field-option-data/template.php (2 hunks)
  • assets/js-templates/form-components.php (2 hunks)
  • assets/js/wpuf-form-builder-components.js (3 hunks)
  • includes/AI/FormGenerator.php (1 hunks)
  • includes/AI/RestController.php (3 hunks)
  • includes/Admin/Forms/Admin_Form_Builder.php (2 hunks)
  • src/css/admin/form-builder.css (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
includes/Admin/Forms/Admin_Form_Builder.php (1)
Lib/WeDevs_Settings_API.php (1)
  • get_option (616-625)
includes/AI/RestController.php (1)
includes/AI/FormGenerator.php (1)
  • generate_field_options (900-963)
admin/form-builder/assets/js/components/field-option-data/index.js (1)
assets/js/subscriptions.min.js (1)
  • wp (21-21)
🪛 Biome (2.1.2)
src/css/admin/form-builder.css

[error] 355-355: expected } but instead the file ends

the file ends here

(parse)

🪛 PHPMD (2.15.0)
includes/AI/FormGenerator.php

1260-1260: Avoid unused local variables such as '$index'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (8)
src/css/admin/form-builder.css (1)

321-355: The CSS file is correctly structured with balanced braces—no action needed

Verification confirms that the file has 79 matching opening and closing braces, and properly ends with a closing brace after the .wpuf-ai-options-list rule. The new AI modal styles (lines 321–355) are all well-formed. The Biome parse error reported is either a false positive or was resolved. No fixes are required.

assets/js/wpuf-form-builder-components.js (2)

653-660: AI state initialization looks good.

The new AI-related state properties are properly initialized with appropriate default values.


672-676: LGTM on computed property.

The all_ai_selected computed correctly checks if all AI-generated options are selected.

admin/form-builder/assets/js/components/field-option-data/index.js (2)

19-26: AI state initialization is consistent.

The AI-related state properties are properly initialized and match the bundled component.


154-157: Nonce validation is correctly implemented server-side.

The AJAX handler at includes/AI/RestController.php:720 properly validates the nonce using check_ajax_referer( 'form-builder-setting-nonce', 'nonce' ), and the nonce is created with wp_create_nonce( 'form-builder-setting-nonce' ) in the form builder localization data. The action name and parameter name match correctly between client and server.

includes/AI/FormGenerator.php (3)

900-963: Good structure for the new AI options generation method.

The method properly saves and restores provider settings, handles errors gracefully, and delegates to provider-specific implementations. The try-catch ensures consistent error responses.


974-1009: System prompt is well-structured.

The prompt clearly defines the task, output format, and constraints. The max_options limit is properly communicated to the AI.


1214-1241: JSON parsing handles edge cases well.

The parse_options_response method properly handles markdown code blocks, extracts JSON from mixed content, and returns structured error responses on parse failures.

Comment on lines +788 to +814
wp.ajax.post('wpuf_ai_generate_field_options', {
prompt: this.ai_prompt,
field_type: field_type,
nonce: wpuf_form_builder.nonce
}).done(function(response) {
// wp.ajax.post returns data directly in response (not response.data)
// when using wp_send_json_success(['options' => $options])
var options = response.options || (response.data && response.data.options) || [];

if (options.length > 0) {
var mapped_options = options.map(function(opt) {
return {
label: opt.label || opt,
value: opt.value || opt,
selected: true
};
});
self.$set(self, 'ai_generated_options', mapped_options);
} else {
self.ai_error = response.message || (response.data && response.data.message) || self.i18n.something_went_wrong;
}
}).fail(function(error) {
self.ai_error = error.message || self.i18n.something_went_wrong;
}).always(function() {
self.ai_loading = false;
});
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Consider sanitizing the prompt before sending.

The ai_prompt is sent directly to the server. While server-side sanitization should handle this, trimming and basic validation on the client side provides defense in depth and better UX.

Additionally, ensure the i18n.something_went_wrong key exists in your localization object to avoid showing undefined on errors.


🏁 Script executed:

#!/bin/bash
# Check if something_went_wrong is defined in localization
rg -n "something_went_wrong" --type=php

Length of output: 0


I need to broaden the search to find where the i18n localization object is defined. Let me check the JavaScript codebase and look for how i18n.something_went_wrong is populated.


---

🏁 Script executed:

```shell
#!/bin/bash
# Search for i18n definitions and something_went_wrong across all file types
rg -n "something_went_wrong" -g "*.js" -g "*.php" -C 3

Length of output: 2146


🏁 Script executed:

#!/bin/bash
# Look for i18n object assignments/initialization in JS
rg -n "i18n\s*[=:]" --type=js -C 2

Length of output: 1520


🏁 Script executed:

#!/bin/bash
# Check the specific file for i18n initialization
grep -n "i18n" assets/js/wpuf-form-builder-components.js | head -20

Length of output: 1507


I need to find where wpuf_form_builder.i18n is populated via PHP localization. Let me search for the wp_localize_script calls.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find where wpuf_form_builder is localized
rg -n "wp_localize_script.*wpuf_form_builder|wpuf_form_builder.*wp_localize_script" --type=php -C 3

Length of output: 665


🏁 Script executed:

#!/bin/bash
# Search for i18n array definitions in PHP files
rg -n "i18n.*=.*array|'i18n'\s*=>" --type=php -C 5

Length of output: 3984


🏁 Script executed:

#!/bin/bash
# Search specifically for something_went_wrong in all file types (case-insensitive)
rg -in "something_went_wrong"

Length of output: 659


I need to find the i18n() method that defines the localization keys. Let me search for it.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find the i18n() method definition in Admin_Form_Builder
rg -n "function i18n|public function i18n" includes/Admin/Forms/Admin_Form_Builder.php -A 30

Length of output: 2412


🏁 Script executed:

#!/bin/bash
# Search for any AI-related i18n keys in the file
rg -n "ai_error|ai_generated|ai_" includes/Admin/Forms/Admin_Form_Builder.php --type=php

Length of output: 719


Let me get the complete i18n() method and check how ai_prompt is handled:


---

🏁 Script executed:

```shell
#!/bin/bash
# Get full i18n() method
rg -n "private function i18n" includes/Admin/Forms/Admin_Form_Builder.php -A 100 | head -150

Length of output: 5985


🏁 Script executed:

#!/bin/bash
# Check how ai_prompt is defined and used in JS
rg -n "ai_prompt" assets/js/wpuf-form-builder-components.js -B 2 -A 2

Length of output: 918


Add something_went_wrong to the i18n localization and trim the ai_prompt before sending.

  1. The i18n.something_went_wrong key is not defined in the localization array returned by the i18n() method in includes/Admin/Forms/Admin_Form_Builder.php (lines 330–363). This will result in undefined being displayed in error messages at lines 807 and 810. Add the key to the localization array.

  2. The ai_prompt validation at line 779 checks if (!this.ai_prompt.trim()) but does not actually trim the value before sending it at line 789. Trim the prompt before posting: prompt: this.ai_prompt.trim().

🤖 Prompt for AI Agents
In assets/js/wpuf-form-builder-components.js around lines 788–814: the code uses
i18n.something_went_wrong (lines ~807 and ~810) which isn’t defined in the PHP
localization array, and it sends this.ai_prompt without trimming it; to fix, add
"something_went_wrong" to the i18n localization array returned by
includes/Admin/Forms/Admin_Form_Builder.php (lines ~330–363) with an appropriate
message, and change the AJAX payload to send the trimmed prompt (use
this.ai_prompt.trim()) so the posted prompt matches the validation.

Comment on lines +181 to +189
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align ai_configured detection with stored wpuf_ai option structure

Right now ai_api_key is derived only from $ai_settings[ $ai_provider . '_api_key' ], but includes/AI/RestController::save_settings() persists a generic ai_api_key key into the same wpuf_ai option. If only ai_api_key is set (which is the default behavior), ai_configured will be false and the UI will show “AI not configured” even though the provider is usable.

Consider falling back to ai_api_key when the provider‑specific key is missing so both paths stay in sync:

-        $ai_settings   = get_option( 'wpuf_ai', [] );
-        $ai_provider   = isset( $ai_settings['ai_provider'] ) ? $ai_settings['ai_provider'] : '';
-        $ai_model      = isset( $ai_settings['ai_model'] ) ? $ai_settings['ai_model'] : '';
-        $provider_key  = $ai_provider . '_api_key';
-        $ai_api_key    = isset( $ai_settings[ $provider_key ] ) ? $ai_settings[ $provider_key ] : '';
+        $ai_settings   = get_option( 'wpuf_ai', [] );
+        $ai_provider   = isset( $ai_settings['ai_provider'] ) ? $ai_settings['ai_provider'] : '';
+        $ai_model      = isset( $ai_settings['ai_model'] ) ? $ai_settings['ai_model'] : '';
+        $provider_key  = $ai_provider . '_api_key';
+        $ai_api_key    = isset( $ai_settings[ $provider_key ] )
+            ? $ai_settings[ $provider_key ]
+            : ( $ai_settings['ai_api_key'] ?? '' );

This keeps the new ai_configured flag accurate regardless of whether keys are stored per‑provider or generically.

Also applies to: 220-221

Comment on lines +955 to +962
} catch (\Exception $e) {
return [
'success' => false,
'error' => true,
'message' => $e->getMessage(),
'provider' => $this->current_provider
];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Exception handler doesn't restore provider settings.

If an exception occurs, the original provider settings are not restored because the restoration code (lines 935-937) is only reached on success. Consider using a finally-like pattern.

Apply this diff to ensure settings are always restored:

         } catch (\Exception $e) {
+            // Restore original provider settings
+            $this->current_provider = $original_provider;
+            $this->current_model = $original_model;
+            $this->api_key = $original_api_key;
+
             return [
                 'success' => false,
                 'error' => true,
                 'message' => $e->getMessage(),
                 'provider' => $this->current_provider
             ];
         }
🤖 Prompt for AI Agents
In includes/AI/FormGenerator.php around lines 935 to 962, the catch block at
955–962 returns on exception without restoring the original provider settings
(restoration currently only runs on success at lines 935–937); update the
control flow so the provider settings are always restored—either move the
restoration logic into a finally-style block that executes after the try/catch
or call the same restore routine at the start of the catch before returning,
ensuring the original provider and any related state are set back regardless of
success or failure.

Comment on lines +1144 to +1148
private function call_google_for_options($system_prompt, $user_prompt, $options = []) {
$model_config = $this->get_model_config('google', $this->current_model);

$endpoint = str_replace('{model}', $this->current_model, $this->provider_configs['google']['endpoint']);
$endpoint .= '?key=' . $this->api_key;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

API key exposed in URL query string.

While this is Google's documented approach for server-side calls, API keys in URLs can appear in server logs. Consider if there are alternative authentication methods or ensure server logs are properly secured.


🌐 Web query:

Google Gemini API authentication methods 2024

💡 Result:

Summary — Gemini API authentication methods (as of 2024, and still reflected in current docs):

  • API key (x-goog-api-key) — primary/simple method for the Google AI Studio / Generative Language endpoints. Best for server-side testing; include in request header. (ai.google.dev)
  • OAuth 2.0 — supported for apps requiring delegated, user-scoped access and stricter controls. (ai.google.dev)
  • Service accounts / Application Default Credentials (ADC) — used for server-to-server and production use on Google Cloud (Vertex AI / Gemini Enterprise) via ADC / service-account tokens. (cloud.google.com)
  • Ephemeral tokens — for direct client-side access to the Live API (short-lived tokens to reduce risk). (ai.google.dev)

Note: API-key support differs by endpoint — Generative Language (ai.google.dev / generativelanguage.googleapis.com) accepts API keys; many Google Cloud Vertex AI endpoints typically expect ADC/service-account or OAuth tokens (API-key behavior varies and can be rejected by some Vertex endpoints). Test the specific endpoint you use. (ai.google.dev)

If you want, I can paste short curl examples for each method.

Citations:


Implement recommended Google Gemini API authentication using request headers instead of URL parameters.

Google's documented approach for Generative Language endpoints uses the API key in the request header (x-goog-api-key), not as a URL query parameter. The current implementation exposes the API key in the endpoint URL (?key=...), which can appear in server logs, proxy logs, and browser history.

Update the code to pass the API key via the request header instead of appending it to the endpoint URL. This requires modifying both the endpoint construction and the HTTP request logic to include the appropriate header authentication.

🤖 Prompt for AI Agents
In includes/AI/FormGenerator.php around lines 1144 to 1148, the code appends the
API key to the endpoint URL (?key=...), which exposes the key; remove the query
parameter from the constructed endpoint and instead add the API key as the
x-goog-api-key request header when performing the HTTP call; update the HTTP
request logic that sends the request for Google Gemini (curl/stream
context/Guzzle) to include 'x-goog-api-key: {your_api_key}' in headers, and
ensure no other code logs or echoes the API key.

Comment on lines +62 to 63
add_action( 'wp_ajax_wpuf_ai_generate_field_options', [ $this, 'ajax_generate_field_options' ] );
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unify REST and AJAX behaviors for AI field options and harden error handling

The new REST route and AJAX action for generating field options are well-scoped (cap checks, nonce check, bounded max_options) and the REST path correctly normalizes options via sanitize_field_options().

Two small follow-ups would make this more robust and consistent:

  1. Reuse sanitize_field_options() for AJAX responses

Right now the REST path returns sanitized key => label pairs, while the AJAX path returns raw $result['options']:

// REST
$sanitized_options = $this->sanitize_field_options( $result['options'] ?? [] );
return new WP_REST_Response( [ 'options' => $sanitized_options, ... ] );

// AJAX
if ( isset( $result['success'] ) && $result['success'] ) {
    wp_send_json_success( [ 'options' => $result['options'] ] );
}

For consistent consumer behavior (and a safer baseline if AI ever emits unexpected content), consider running the AJAX result through the same sanitizer:

-        if ( isset( $result['success'] ) && $result['success'] ) {
-            wp_send_json_success( [ 'options' => $result['options'] ] );
-        }
+        if ( isset( $result['success'] ) && $result['success'] ) {
+            $options = $this->sanitize_field_options( $result['options'] ?? [] );
+            wp_send_json_success( [ 'options' => $options ] );
+        }
  1. Also treat success === false as an error in REST handler

Per the FormGenerator::generate_field_options() doc, some error paths may only set 'success' => false without 'error' => true. To avoid returning a “success” payload with an empty options array and a success message in those cases, you could broaden the check:

-            if ( isset( $result['error'] ) && $result['error'] ) {
+            if ( ( isset( $result['error'] ) && $result['error'] )
+                || ( isset( $result['success'] ) && ! $result['success'] ) ) {
                 return new WP_Error(
                     'generation_failed',
                     $result['message'] ?? __( 'Failed to generate field options', 'wp-user-frontend' ),
                     [ 'status' => 400 ]
                 );
             }

These tweaks keep both entry points aligned and ensure callers never get a “success” response for a failed generation.

Also applies to: 243-283, 629-745

🤖 Prompt for AI Agents
In includes/AI/RestController.php around lines 62-63, the AJAX handler returns
raw $result['options'] while the REST route sanitizes options via
sanitize_field_options(), and the REST handler only treats error when 'error'
=== true but some failures set 'success' => false; update the AJAX success
branch to pass $result['options'] (or [] if missing) through
$this->sanitize_field_options() before wp_send_json_success, and adjust the REST
handler error check to treat both 'error' === true OR 'success' === false as
failure so it returns an error response instead of a success payload; apply the
same sanitization + broadened error-checking fixes to the other affected ranges
noted (lines 243-283 and 629-745).

@Rubaiyat-E-Mohammad Rubaiyat-E-Mohammad added QA Approved This PR is approved by the QA team and removed needs: testing labels Nov 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs: dev review This PR needs review by a developer QA Approved This PR is approved by the QA team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants