Skip to content

Conversation

@arifulhoque7
Copy link
Contributor

@arifulhoque7 arifulhoque7 commented Sep 5, 2025

Close issue , issue , issue ,issue

🎨 AI Form Builder – Design & Implementation

πŸ“‹ Overview

This PR introduces a groundbreaking AI-powered form builder for WP User Frontend, allowing users to create complex WordPress forms through natural language descriptions. It reimagines the traditional form creation workflow by integrating a conversational, intelligent interface that adapts to user intent and provides real-time refinement options.

The feature is built with a mix of AI orchestration, Vue.js components, modern styling, and secure REST APIs, transforming WP User Frontend into a next-generation, AI-assisted platform.


πŸš€ Major Additions

🧠 AI Infrastructure (+8,500 lines)

  • Core AI System – Multiple provider support with centralized orchestration.
  • Predefined Provider – 1,189 lines of intelligent form pattern recognition.
  • Form Generator – 577 lines of context-aware generation logic.
  • REST API Controller – 1,004 lines of secure API endpoints.
  • AI Manager – Unified service handling for AI interactions.

πŸ–₯️ Vue.js Components (+2,825 lines)

  • AIFormBuilder.vue – Main orchestration with 3-stage workflow.
  • FormInputStage.vue – Natural language form input interface.
  • FormProcessingStage.vue – Real-time form generation progress feedback.
  • FormSuccessStage.vue – Conversational refinement & instant form preview.

🎨 Styling & UI (+8,656 lines)

  • ai-form-builder.css – New styling system (7,636 lines).
  • form-builder.css – Enhanced form builder visuals (1,020 lines).
  • Tailwind CSS Integration – Clean, modern UI components.
  • Animations – Interactive confetti and visual feedback.

πŸ“š Documentation & References (+3,704 lines)

  • Implementation Plan (1,847 lines).
  • System Prompts & AI Context (454 lines).
  • Field Reference Documentation (1,403 lines).
  • Comprehensive Testing Utilities for reproducible results.

✨ Key Features

  1. Natural Language Processing

    • Understands complex requirements.
    • Detects field types automatically.
    • Provides context-aware suggestions.
  2. Three-Stage Workflow

    • Input β†’ Processing β†’ Success/Refinement.
    • Smooth progression with visual feedback.
  3. Interactive Chat Refinement

    • Conversational field adjustments.
    • Real-time preview updates.
  4. Smart Field Mapping

    • Supports 30+ field types.
    • WordPress-specific fields (posts, taxonomies).
    • Handles Pro-only features gracefully.
  5. Settings Integration

    • Configurable AI providers.
    • Temperature & token controls.
    • API key management.

πŸ”§ Technical Enhancements

  • Performance – Optimized build process with Vite.
  • Security – Nonce-based authentication, sanitized inputs.
  • Testing – Utilities for AI response simulation.
  • Error Handling – Graceful fallbacks, user-friendly messages.
  • Internationalization (i18n) – Full multi-language support.

πŸ“Š Stats

  • Files Changed: 39
  • Additions: 19,666
  • Deletions: 68
  • Net Change: +19,598

πŸ§ͺ Testing Files

  • check-ai-status.php – AI availability checker.
  • test-ai-context.php – AI context validation.
  • test-google-gemini.php – Google AI provider tests.
  • setup-google-ai.php – Setup and config helper.

🎯 Impact

This feature elevates WP User Frontend into an AI-assisted form creation platform, bridging the gap between beginner-friendly usability and professional-grade customization. Users can now describe forms in plain English and instantly receive production-ready WordPress forms, making form creation faster, smarter, and more accessible.

Summary by CodeRabbit

  • New Features

    • AI Form Builder: multi-stage UI to generate, preview, chat-update, persist, and edit forms from prompts.
    • REST endpoints and admin tooling to generate, test providers, manage settings, and create/modify AI-generated forms.
    • Offline "predefined" provider for local testing and dynamic model filtering in settings.
  • Style

    • Large Tailwind/CSS expansion with UI utilities, chat/form styles, RTL and responsive refinements.
  • Documentation

    • Full implementation plan, system prompt, and WPUF field/reference guides.
  • Chores

    • Build/watch pipeline, asset entries and build scripts added for AI assets.
  • Tests

    • Diagnostic and test harnesses for provider and conversation-context validation.

sapayth and others added 25 commits September 3, 2025 16:22
Introduces a comprehensive Markdown reference for all available WPUF (WordPress User Frontend) post form fields, including field types, properties, options, Pro vs Free distinctions, validation rules, templates, and usage guidelines for AI-driven form creation
@arifulhoque7 arifulhoque7 requested a review from sapayth September 5, 2025 08:29
@arifulhoque7 arifulhoque7 self-assigned this Sep 5, 2025
@arifulhoque7 arifulhoque7 added needs: testing needs: dev review This PR needs review by a developer labels Sep 5, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 5, 2025

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

πŸ’€ Files selected but had no reviewable changes (1)
  • assets/js/components/FormSuccessStage.vue
β›” Files ignored due to path filters (1)
  • assets/js/ai-form-builder.min.js is excluded by !**/*.min.js

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a complete AI Form Builder feature: backend AI orchestration (multi-provider FormGenerator, PredefinedProvider), REST API, admin handlers/templates, Vue frontend with components and build pipeline updates, new assets/styles, settings UI, diagnostic/test scripts, and plugin bootstrap wiring.

Changes

Cohort / File(s) Summary
Planning & Prompts
AI_FORM_BUILDER_IMPLEMENTATION_PLAN.md, includes/AI/system-prompt.md, includes/AI/wpuf-post-form-fields-reference.md
New implementation plan and strict system prompt/field reference documents defining JSON schema, validation checklist, field mappings, examples, and AI guidance for WPUF forms.
AI Core & Orchestration
includes/AI/FormGenerator.php, includes/AI/RestController.php, Lib/AI/PredefinedProvider.php, includes/AI_Manager.php
New FormGenerator service (multi-provider: OpenAI/Anthropic/Google/predefined), REST controller with routes for generate/test/providers/settings/create/modify, PredefinedProvider for local testing, and AI_Manager to wire hooks, localization and component access.
Admin Integration & Handlers
includes/Admin.php, includes/Admin/Forms/AI_Form_Handler.php, includes/Admin/template-parts/ai-form-builder.php, includes/Admin/template-parts/modal-v4.2.php
Admin wiring: container AI form handler entry; handlers for AI template/generating/success flows; Vue mount template; AI template tile and AI config modal injected into template grid; updated modal JS/CSS integration.
Settings UI
includes/functions/settings-options.php
Adds "AI Settings" section and fields: ai_provider, ai_model (extendable via filter), and ai_api_key.
Frontend Vue App & Components
assets/js/ai-form-builder.js, assets/js/components/AIFormBuilder.vue, assets/js/components/FormInputStage.vue, assets/js/components/FormProcessingStage.vue, assets/js/wpuf-admin.js
New Vue bootstrap and components implementing input → processing → success/chat stages, preview/apply flows, session handling, field mapping helpers, pro-gating modals, and admin AI provider→model filtering logic.
Assets & Styles
includes/Assets.php, src/css/ai-form-builder.css, assets/css/admin/form-builder.css
Registers ai-form-builder CSS/JS handles; adds Tailwind-based ai-form-builder.css (spin animation) and extensive admin CSS expansions (form controls, chat UI, utilities, RTL/responsive rules).
Build Pipeline
Gruntfile.js, package.json, vite.config.mjs, tailwind.config.js
Vite entry added for ai-form-builder, npm script build:ai-form-builder, Grunt tailwind mapping/watch tasks and new watchers, Tailwind content scanning extended to include PHP templates.
Diagnostics & Tests
check-ai-status.php, setup-google-ai.php, test-ai-context.php, test-google-context.php, test-google-gemini.php
New setup/status/testing scripts and harnesses for configuring/exercising Google Gemini and predefined provider flows; quick status and functional checks.
Admin Asset Enqueue & Templates
includes/Assets.php, includes/Admin/template-parts/ai-form-builder.php, includes/Admin.php
Asset registration and localized data injection for AI pages; admin enqueue helper added; template mount point localizes boot data via wp_localize_script.
Bootstrap & Small Edits
wpuf.php, includes/class-frontend-render-form.php, includes/Admin/Posting.php
Plugin bootstrap loads AI_Manager and registers container entry; minor whitespace and logging removals.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

Suggested reviewers

  • sapayth

Pre-merge checks (2 passed, 1 inconclusive)

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The title mentions the AI form builder but includes a branch-style prefix and lacks clarity and conciseness, making it too generic to clearly summarize the primary implementation details. Rewrite the title to a concise, descriptive sentence focused on the main change, for example β€œAdd AI-powered form builder design and integration.”
βœ… Passed checks (2 passed)
Check name Status Explanation
Description Check βœ… Passed The description is thorough, detailed, and directly related to the changeset, clearly outlining the new AI form builder’s objectives, components, and implementation plan.
Docstring Coverage βœ… Passed Docstring coverage is 92.21% which is sufficient. The required threshold is 80.00%.

Poem

I tapped my paws on keys so bright,
I spun up prompts beneath moonlight.
From REST to Vue I hopped with glee,
Fields and CSS bloomed merrily.
A rabbit's nudge β€” now go create! πŸ‡βœ¨

✨ Finishing Touches
πŸ§ͺ Generate unit tests
  • 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: 49

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
includes/Admin/Posting.php (1)

156-163: Register or guard localization of β€˜wpuf-field-initialization’
The handle β€˜wpuf-field-initialization’ isn’t registered in includes/Assets.php, so calling wp_localize_script() on it will enqueue data that never prints. Either add a corresponding entry to get_scripts() (so β€˜wpuf-field-initialization’ is registered) or wrap the wp_localize_script() call in a check, for example:

if ( wp_script_is( 'wpuf-field-initialization', 'registered' ) ) {
    wp_localize_script( 'wpuf-field-initialization', 'wpuf_field_initializer', [
        'asset_uri' => defined( 'WPUF_PRO_ASSET_URI' ) ? WPUF_PRO_ASSET_URI : '',
    ] );
}

Location: includes/Admin/Posting.php:157–160

🧹 Nitpick comments (58)
tailwind.config.js (1)

12-13: LGTM β€” broadened content globs ensure PHP template classes are scanned.

Consider narrowing if build times spike (e.g., target only includes/Admin/template-parts and other known template dirs) or use a safelist for dynamic classes.

src/css/ai-form-builder.css (1)

13-16: LGTM β€” scoped spin animation with wpuf- prefix.

Consider honoring reduced motion:

@media (prefers-reduced-motion: reduce) {
  .wpuf-animate-spin { animation: none; }
}
setup-google-ai.php (1)

7-14: Support env override to avoid editing the file.

Allow running via WPUF_GOOGLE_API_KEY=... wp eval-file ....

-// REPLACE THIS WITH YOUR ACTUAL API KEY
-$api_key = 'YOUR_GOOGLE_API_KEY_HERE';
+// Prefer environment variable if present
+$api_key = getenv('WPUF_GOOGLE_API_KEY') ?: 'YOUR_GOOGLE_API_KEY_HERE';
test-google-gemini.php (1)

101-114: Improve error reporting for non-200 responses.

Surface cURL errors and HTTP reason text to speed up debugging.

 } else {
-    echo "❌ ERROR: Request failed\n";
-    $error_data = json_decode($response, true);
+    echo "❌ ERROR: Request failed\n";
+    $curl_err = isset($ch) ? '' : '';
+    $error_data = json_decode($response, true);
     if (isset($error_data['error'])) {
         echo "Error: " . $error_data['error']['message'] . "\n";
         echo "Status: " . $error_data['error']['status'] . "\n";
         
         if (strpos($error_data['error']['message'], 'API key not valid') !== false) {
             echo "\n⚠️  Please check your API key!\n";
             echo "Get one at: https://aistudio.google.com/\n";
         }
     } else {
-        echo "Response: $response\n";
+        echo "Response body: $response\n";
     }
 }

Additionally, check and print curl_error($ch) when $response === false.

includes/functions/settings-options.php (1)

655-685: Model list: prefer stable β€œlatest” aliases and verify defaults.

Several IDs (e.g., gpt-3.5-turbo, older dated Anthropic models) may be deprecated or superseded. Consider using each provider’s β€œlatest” aliases and setting a sensible default (e.g., gpt-4o-mini or gemini-1.5-flash) to reduce support churn.

Would you like me to switch to β€œlatest” aliases and gate model options by selected provider (to avoid cross-provider IDs in the dropdown)?

assets/css/admin/form-builder.css (4)

1209-1211: Duplicate transition declarations in same rule.

Two transition declarations override each other. Merge into one to satisfy linters.

-  transition: visibility 0.2s;
-  transition: padding 0.2s ease-out,
-    background-color 0.2s ease-out;
+  transition: visibility 0.2s, padding 0.2s ease-out, background-color 0.2s ease-out;

Note: If this file is generated, fix in the source (Tailwind/CSS) or suppress the linter for generated assets.


3899-3901: visibility: collapse on non-table elements is inconsistent across browsers.

Use display: none or visibility: hidden depending on layout needs.

-.wpuf-collapse {
-  visibility: collapse;
-}
+.wpuf-collapse { visibility: hidden; } /* or display: none; if layout permits */

7075-7113: Hard-coded green color for range slider; use theme tokens.

Replace raw green with your design tokens to support theming (light/dark/brand).

-input[type="range"] {
-  ... 
-  background: green;
-  ...
-}
+input[type="range"] {
+  ...
+  background: var(--fallback-su, oklch(var(--su)/1));
+  ...
+}
-input[type="range"]::-webkit-slider-thumb {
-  background: green;
-}
+input[type="range"]::-webkit-slider-thumb {
+  background: var(--fallback-su, oklch(var(--su)/1));
+}
-input[type="range"]::-moz-range-thumb {
-  background: green;
-}
+input[type="range"]::-moz-range-thumb {
+  background: var(--fallback-su, oklch(var(--su)/1));
+}

2661-2786: Collapse control: multiple duplicate transition timing declarations.

Several transition-timing-function lines repeat; keep one to reduce CSS size and appease linters.

-  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-  transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);

If compiled by Tailwind/DaisyUI, consider post-processing with cssnano to dedupe in dist files.

Lib/AI/PredefinedProvider.php (4)

818-821: Don’t overwrite response['fields'] with WPUF fields

Overwriting the generic fields with WPUF-shaped fields can break consumers expecting the original shape. Keep fields as-is and expose only wpuf_fields (optionally include original_fields for debugging).

Apply this diff to stop overriding fields:

-        // Update the response fields to show the converted WPUF fields in debug logs
-        // This ensures RestController logs show the correct field structure
-        $response['fields'] = $wpuf_fields;
+        // Keep original `fields` intact; `wpuf_fields` already contains the converted structure.

If you still want debug visibility, capture the original before conversion and add original_fields:

// place before conversion
$original_fields = $response['fields'];

// after conversion
$response['original_fields'] = $original_fields;

1023-1040: Taxonomy field may be missing required props and has mixed boolean convention

WPUF taxonomy fields typically need the taxonomy slug (often via name or a dedicated taxonomy key) and toggle values as 'yes'/'no'. Here show_inline is set to false (boolean), while other flags use 'no'. Also consider explicitly setting the taxonomy to 'category'.

Proposed adjustments:

-                    $wpuf_field['show_inline'] = false;
+                    $wpuf_field['show_inline'] = 'no';
+                    // Ensure taxonomy slug is explicit if WPUF expects it
+                    $wpuf_field['taxonomy'] = 'category';

Please verify against WPUF’s taxonomy field schema to confirm the expected keys.


1134-1187: Prefix WP global functions for clarity and slight perf (FQN) + ensure availability

In namespaced PHP, using global WP functions via FQNs is clearer and avoids an extra global lookup. Apply to __(), get_option(), current_time(), etc.

Apply representative changes:

-            'message_restrict' => __('This page is restricted. Please {login} / {register} to view this page.', 'wp-user-frontend'),
+            'message_restrict' => \__('This page is restricted. Please {login} / {register} to view this page.', 'wp-user-frontend'),
-            'submit_text' => sprintf(__('Create %s', 'wp-user-frontend'), $title),
+            'submit_text' => \sprintf(\__('Create %s', 'wp-user-frontend'), $title),
-                'new_to' => get_option('admin_email', ''),
-                'new_subject' => sprintf(__('New %s has been created', 'wp-user-frontend'), strtolower($title)),
+                'new_to' => \get_option('admin_email', ''),
+                'new_subject' => \sprintf(\__('New %s has been created', 'wp-user-frontend'), \strtolower($title)),

Also update earlier current_time('mysql') similarly:

-        $response['generated_at'] = current_time('mysql');
+        $response['generated_at'] = \current_time('mysql');

949-981: Map consistency between input_type and template; avoid later overrides

Some types (e.g., 'number', 'file', 'image') are remapped again in addFieldSpecificProperties, contradicting the β€œdon’t override” comment. Keep mapping in one place (here) and make addFieldSpecificProperties additive only.

Simplify addFieldSpecificProperties by removing input_type/template reassignments and centralize the mapping here to reduce drift.

assets/js/components/FormProcessingStage.vue (6)

125-125: Use WordPress JS i18n when available and support domain argument

Rely on wp.i18n.__ if present and pass through extra args. Current method ignores the text-domain parameter.

Apply:

-        __: window.__ || ((text) => text),
+        __(...args) {
+            const fn = window?.wp?.i18n?.__ || ((t) => t);
+            return fn(...args);
+        },

136-152: Clear timers on unmount to prevent leaks and stray emits

Track setTimeout IDs and clear them on beforeUnmount. This component schedules multiple timers and currently never clears them.

Apply:

-            steps.forEach((step, index) => {
-                setTimeout(() => {
+            this._timers = this._timers || [];
+            steps.forEach((step, index) => {
+                const id = setTimeout(() => {
                     this.currentStep = step;
                     
                     if (step === 4) {
-                        setTimeout(() => {
+                        const cid = setTimeout(() => {
                             this.showConfetti = true;
                             this.confettiUrl = this.getConfettiUrl();
                             
-                            setTimeout(() => {
+                            const fid = setTimeout(() => {
                                 this.$emit('generation-complete');
                                 this.isProcessing = false;
-                            }, this.completeDelay);
-                        }, this.confettiDelay);
+                            }, this.completeDelay);
+                            this._timers.push(fid);
+                        }, this.confettiDelay);
+                        this._timers.push(cid);
                     }
-                }, (index + 1) * this.stepDelay);
+                }, (index + 1) * this.stepDelay);
+                this._timers.push(id);
             });

Add this hook near the end of the script:

beforeUnmount() {
    (this._timers || []).forEach(clearTimeout);
    this._timers = [];
}

2-3: Improve accessibility: announce progress and busy state

Add ARIA to help screen readers track progress.

Apply:

-    <div class="wpuf-ai-form-wrapper wpuf-font-sans wpuf-bg-white wpuf-w-full wpuf-h-screen wpuf-overflow-hidden wpuf-relative">
+    <div class="wpuf-ai-form-wrapper wpuf-font-sans wpuf-bg-white wpuf-w-full wpuf-h-screen wpuf-overflow-hidden wpuf-relative"
+         role="status" :aria-busy="isProcessing.toString()">

85-85: Decorative confetti image should have empty alt

Avoids unnecessary announcements by screen readers.

Apply:

-                <img :src="confettiUrl" alt="Confetti" class="wpuf-w-full wpuf-h-full wpuf-object-cover"/>
+                <img :src="confettiUrl" alt="" class="wpuf-w-full wpuf-h-full wpuf-object-cover" />

92-115: Declare emitted events

Declare generation-complete for better DX and Vue 3 compatibility (dev warnings).

Apply:

 export default {
     name: 'FormProcessingStage',
+    emits: ['generation-complete'],
     props: {

7-16: Inline base64 image in the SVG is very large; consider replacing

The embedded 276Γ—276 PNG inflates bundle size. Prefer a lightweight SVG/CSS spinner or a small external asset processed by Vite.

I can provide a minimal CSS spinner replacement if helpful.

vite.config.mjs (1)

21-47: Set an explicit build target to ensure optional chaining is transpiled

The component uses optional chaining (?.). Explicit build.target helps guarantee compatibility with older admin browsers if needed.

Apply:

         build: {
+            target: 'es2018',
             rollupOptions: {
wpuf.php (1)

165-166: Prefer WPUF_INCLUDES and keep includes consistent

Use the existing WPUF_INCLUDES constant for consistency with the rest of the codebase.

-        require_once __DIR__ . '/includes/AI_Manager.php';
+        require_once WPUF_INCLUDES . '/AI_Manager.php';
assets/js/wpuf-admin.js (1)

235-237: Localize β€œSelect AI Model”

Hard-coded English string. Use wp.i18n.__ or a localized string injected from PHP.

test-ai-context.php (2)

99-103: Normalize provider usage or update the comment

Comment says β€œTest with Google Gemini” but provider is taken from settings. Either set provider explicitly or adjust the label.

Apply one:

-// Test with Google Gemini
+// Test with current provider configured in WPUF settings

Or:

 $options = [
     'conversation_context' => $scenario['context'],
-    'session_id' => 'test_session_' . time()
+    'session_id' => 'test_session_' . time(),
+    'provider' => 'google'
 ];

101-101: Use a collision-resistant session id

time() can collide within a loop. Prefer uniqid with entropy.

-        'session_id' => 'test_session_' . time()
+        'session_id' => uniqid('test_session_', true)
includes/Admin/template-parts/ai-form-builder.php (3)

27-29: Preserve punctuation/newlines for prompt/description

Prompts/descriptions can be multi-line; sanitize_textarea_field keeps intent while preventing XSS.

-$description = isset( $_GET['description'] ) && ! empty( $_GET['description'] ) ? sanitize_text_field( wp_unslash( $_GET['description'] ) ) : '';
-$prompt = isset( $_GET['prompt'] ) && ! empty( $_GET['prompt'] ) ? sanitize_text_field( wp_unslash( $_GET['prompt'] ) ) : '';
+$description = isset( $_GET['description'] ) && ! empty( $_GET['description'] ) ? sanitize_textarea_field( wp_unslash( $_GET['description'] ) ) : '';
+$prompt = isset( $_GET['prompt'] ) && ! empty( $_GET['prompt'] ) ? sanitize_textarea_field( wp_unslash( $_GET['prompt'] ) ) : '';

38-43: Harden localized URLs

Minor: mirror rest_url escaping for siteUrl as well.

-        'siteUrl'      => site_url(),
+        'siteUrl'      => esc_url_raw( site_url() ),

57-64: Tailwind arbitrary class safelisting

wpuf-pr-[20px] may be purged by JIT if not safelisted. Ensure it’s included in Tailwind safelist.

includes/AI/system-prompt.md (1)

435-454: Markdown structure nits

Use proper headings (##) instead of emphasis for sections; helps maintainers and linters (MD036).

test-google-context.php (3)

52-65: Prefer systemInstruction for Gemini system prompts

Passing the entire system prompt as user content works but is suboptimal. Use systemInstruction to improve adherence.

-    $body = [
-        'contents' => [
-            [
-                'parts' => [
-                    ['text' => $full_prompt]
-                ]
-            ]
-        ],
-        'generationConfig' => [
+    $body = [
+        'systemInstruction' => [
+            'parts' => [['text' => $system_prompt]]
+        ],
+        'contents' => [
+            [
+                'parts' => [
+                    ['text' => (!empty($context) ? "CURRENT CONTEXT:\n" . json_encode($context, JSON_PRETTY_PRINT) . "\n\n" : "") . "User request: " . $prompt]
+                ]
+            ]
+        ],
+        'generationConfig' => [
             'temperature' => 0.7,
             'maxOutputTokens' => 2000,
             'responseMimeType' => 'application/json'
         ]
     ];

73-86: Add cURL error handling and clearer API failures

Improve diagnostics for non-200 responses and cURL errors.

     $response = curl_exec($ch);
+    if ($response === false) {
+        $err = curl_error($ch);
+        curl_close($ch);
+        return ['error' => true, 'message' => 'cURL error: ' . $err];
+    }
     $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
     curl_close($ch);
     
     if ($http_code === 200) {
         $data = json_decode($response, true);
         if (isset($data['candidates'][0]['content']['parts'][0]['text'])) {
             $content = $data['candidates'][0]['content']['parts'][0]['text'];
             return json_decode($content, true);
         }
     }
     
-    return ['error' => true, 'message' => 'API request failed'];
+    return ['error' => true, 'message' => 'API request failed', 'http_code' => $http_code, 'raw' => $response];

132-134: Avoid printing API keys (even partially)

Remove API key echo to prevent accidental leakage in logs.

-echo "API Key: " . substr($API_KEY, 0, 10) . "...***\n";
-echo "Model: gemini-1.5-flash (Free tier)\n";
+echo "Model: gemini-1.5-flash\n";
includes/Admin/template-parts/modal-v4.2.php (5)

154-161: Harden total template count when $registry is null/non-countable.

count() on null emits warnings on older PHP. Guard with is_countable.

-// Base count: registry templates + blank form
-$total_count = count($registry) + 1;
+// Base count: registry templates + blank form
+$total_count = ( isset( $registry ) && is_countable( $registry ) ? count( $registry ) : 0 ) + 1;

197-201: Localize hard‑coded strings (β€œBlank Form”, β€œAI Forms”).

Use translation functions for title and labels.

-<a href="<?php echo esc_url( $blank_form_url ); ?>" class="wpuf-btn-secondary ... " title="<?php echo esc_attr( 'Blank Form' ); ?>">
+<a href="<?php echo esc_url( $blank_form_url ); ?>" class="wpuf-btn-secondary ... " title="<?php echo esc_attr__( 'Blank Form', 'wp-user-frontend' ); ?>">
...
-<p class="wpuf-text-sm ..."><?php echo esc_html( 'Blank Form' ); ?></p>
+<p class="wpuf-text-sm ..."><?php echo esc_html__( 'Blank Form', 'wp-user-frontend' ); ?></p>
-<a href="<?php echo esc_url( $ai_form_url ); ?>" class="wpuf-btn-secondary ... " title="<?php echo esc_attr( 'AI Forms' ); ?>">
+<a href="<?php echo esc_url( $ai_form_url ); ?>" class="wpuf-btn-secondary ... " title="<?php echo esc_attr__( 'AI Forms', 'wp-user-frontend' ); ?>">
...
-<p class="wpuf-text-sm ..."><?php echo esc_html( 'AI Forms' ); ?></p>
+<p class="wpuf-text-sm ..."><?php echo esc_html__( 'AI Forms', 'wp-user-frontend' ); ?></p>

Also applies to: 262-266


450-464: Remove debug console logs before release.

Logs spam the console and may leak state. Gate behind a debug flag or remove.

-                console.log('AI Template found:', $aiTemplate.length);
-                console.log('AI Configured raw:', aiConfigured);
-                console.log('AI Configured type:', typeof aiConfigured);
...
-                    console.log('Attaching AI click handler because AI is NOT configured');
...
-                    console.log('NOT attaching AI click handler - AI is configured or template not found');

575-586: Unify β€œconfigured” detection (string vs boolean).

Make the click guard consistent with init logic.

-                var $template = $( e.target ).closest( '.wpuf-ai-forms-template' );
-                var isConfigured = $template.data( 'ai-configured' ) === 'true';
+                var $template = $( e.target ).closest( '.wpuf-ai-forms-template' );
+                var raw = $template.data( 'ai-configured' );
+                var isConfigured = ( raw === true || raw === 'true' );

379-409: Add modal ARIA attributes for accessibility.

Role and labelling help screen readers.

-<div class="wpuf-ai-config-modal ... " id="ai-config-modal">
+<div class="wpuf-ai-config-modal ... " id="ai-config-modal" role="dialog" aria-modal="true" aria-labelledby="ai-config-modal-title">
...
-        <h2 class="wpuf-text-2xl wpuf-font-medium wpuf-text-center wpuf-text-gray-900 wpuf-mb-4">
+        <h2 id="ai-config-modal-title" class="wpuf-text-2xl wpuf-font-medium wpuf-text-center wpuf-text-gray-900 wpuf-mb-4">

Also applies to: 390-392

assets/js/components/FormInputStage.vue (2)

123-124: Prefer wp.i18n.__ when available.

Use WordPress i18n fallback chain.

-        __: window.__ || ((text) => text),
+        __: ( (window.wp && window.wp.i18n && window.wp.i18n.__) || window.__ || ((text) => text) ),

56-62: Optional: disable button via aria and prevent double submits.

Add aria-busy for accessibility and guard against double clicks.

-                <button 
+                <button 
                     @click="startGeneration"
                     :disabled="!formDescription.trim() || isGenerating"
+                    :aria-busy="isGenerating ? 'true' : 'false'"
                     class="wpuf-px-8 ...
includes/Admin/Forms/AI_Form_Handler.php (1)

49-54: Consistent screen IDs (nit).

You use wpuf-ai-form-generation and wpuf-ai-form-generating; consider standardizing for consistency.

Also applies to: 89-94, 124-129

check-ai-status.php (1)

7-16: Optional: Gate output through WP-CLI when available.

This improves UX (colors, exit codes) and avoids accidental web execution.

-echo "=== WPUF AI Form Builder Status Check ===\n\n";
+if (defined('WP_CLI') && WP_CLI) {
+    \WP_CLI::line('=== WPUF AI Form Builder Status Check ===');
+} else {
+    echo "=== WPUF AI Form Builder Status Check ===\n\n";
+}
includes/AI/FormGenerator.php (2)

104-129: Keep predefined keyword logic in one place to avoid drift.

isPredefinedPrompt hardcodes patterns that can diverge from PredefinedProvider’s templates. Consider delegating detection to the provider or centralizing the keyword list.


372-393: Gemini request shape: prefer system_instruction for clarity.

Bundling system + user into one text works, but using system_instruction plus a separate user message tends to produce cleaner JSON and is closer to docs.

Example shape:

  • system_instruction: { parts: [ { text: $system_prompt } ] }
  • contents: [ { role: "user", parts: [ { text: $prompt } ] } ]
includes/Admin.php (1)

176-212: Localize all required frontend config here (good), but guard against missing settings.

Looks fine. To harden:

  • Cast hasApiKey to bool.
  • Provide the generation route path to avoid frontend guessing.
-                'hasApiKey'  => !empty($ai_settings['ai_api_key']),
+                'hasApiKey'  => ! empty( $ai_settings['ai_api_key'] ) ? true : false,
+                'endpoint'   => 'wpuf/v1/ai-form-builder/generate',
AI_FORM_BUILDER_IMPLEMENTATION_PLAN.md (5)

970-1034: Make the system prompt injectable and single-sourced.

Hardcoding the prompt string here and again in providers risks drift. Centralize via a single AI_System_Prompt::get_form_generation_prompt() and reference it in all formatters/providers.


615-653: Predefined provider: avoid sleep(1) and add nonce/session validation notes.

Sleeping in PHP slows admin requests. If you want simulated latency, do it in the frontend. Also add a note that session IDs are not trusted input and should be validated/sanitized.

- // Simulate processing delay (like real AI)
- sleep(1);
+ // Frontend can simulate latency if needed; avoid delaying PHP requests.

707-730: Avoid logging full provider error bodies in production.

Bodies may include provider error details that can leak into logs. Log sanitized summaries and attach the raw body only when WP_DEBUG_LOG is true.

- error_log('WPUF AI OpenAI HTTP Error: ' . $status_code . ' - ' . $error_body);
+ if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
+   error_log('WPUF AI OpenAI HTTP Error: ' . $status_code);
+ }

Also applies to: 721-725


1533-1535: Rate limiting listed as TODOβ€”document minimal strategy.

Given API cost and abuse risks, include a short section recommending per-user/hour limits via transients keyed by user ID and provider.


1776-1811: Field-structure notes: ensure ToC and taxonomy examples reflect required keys.

Great that you called out toc and taxonomy specifics. Add quick JSON examples showing the minimal valid WPUF payload for each to reduce ambiguity for implementers.

Also applies to: 1812-1826

includes/AI/wpuf-post-form-fields-reference.md (3)

370-372: Free vs Pro classification conflicts (multiple_select, recaptcha, google_map, etc.).

multiple_select listed as Free here but PRO earlier; google_map listed as PRO but appears in β€œReal Estate” examples; confirm actual SKU gating and unify labels. Mark disputed items with a single β€œFree/Pro” source of truth.

Also applies to: 392-418


421-448: Meta key rules: tighten regex guidance.

Docs say β€œOnly letters and underscores allowed,” but examples include numbers; adjust rule to β€œlowercase letters, numbers, underscores,” and provide the actual regex you’ll apply during normalization.

- **Characters**: Only letters and underscores allowed
+ **Characters**: Lowercase letters, numbers, and underscores allowed
+ **Validation regex**: `/^[a-z0-9_]+$/`

Also applies to: 449-455


1236-1345: Settings taxonomy: label a few as Pro-only.

Some settings (payments, subscription packs, conditional logic) are Pro. Add explicit β€œPro” badges to those keys to guide AI output and UI expectations.

Also applies to: 1348-1367

Gruntfile.js (4)

6-8: Tailwind map key is unused; duplication will run the same build twice.

tailwind() ignores the left-hand key and builds per CSS filename. Mapping two different keys to the same CSS (admin/form-builder.css) triggers duplicate builds. Consider unique CSS targets only.

- 'admin/form-builder/views/form-builder-v4.1.php': 'admin/form-builder.css',
- 'admin/form-builder/views/post-form-settings.php': 'admin/form-builder.css',
- 'assets/js/forms-list.js': 'admin/form-builder.css',
+ 'admin/form-builder': 'admin/form-builder.css',

125-134: Overlapping watchers may cause redundant builds.

aiFormBuilderVue and aiFormBuilder both watch assets/js/components/**/*.vue. This will run both npm_build_ai_form_builder and Tailwind builds on the same change. If intentional, ignore; otherwise, split globs or use a single watcher that chains both tasks.

- 'assets/js/components/**/*.vue',
+ // Vue files handled by aiFormBuilderVue watcher

Also applies to: 152-162


301-316: build-tailwind runs ai-form-builder twice.

build-tailwind first builds ai-form-builder.css, then calls tailwind which also builds it via tailwindFileMap. Drop the first step to avoid duplicate work.

- grunt.registerTask( 'build-tailwind', [ 'shell:tailwind:src/css/ai-form-builder.css:assets/css/ai-form-builder.css', 'tailwind' ] );
+ grunt.registerTask( 'build-tailwind', [ 'tailwind' ] );

323-339: Tailwind task: guard against missing Tailwind binary and add failure feedback.

If npx tailwindcss is missing, Grunt fails late. Consider pre-checking and logging a clearer message, or document the requirement in CONTRIBUTING.

includes/AI_Manager.php (1)

45-48: Redundant REST API initialization hook

The init_rest_api method is registered but serves no purpose since the RestController handles its own registration in the constructor.

Remove the redundant hook registration and method:

 private function init_hooks() {
-    add_action('rest_api_init', [$this, 'init_rest_api']);
     add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
     add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
 }
-/**
- * Initialize REST API
- */
-public function init_rest_api() {
-    // REST API is already initialized in RestController constructor
-    // This hook ensures it runs at the right time
-}
assets/js/components/AIFormBuilder.vue (2)

394-394: Pro modal auto-close timeout may be too aggressive

The 15-second auto-close for the pro modal might not give users enough time to read and make a decision.

Consider increasing the timeout or making it configurable:

 // Auto-close after 15 seconds
-setTimeout(closeModal, 15000);
+// Auto-close after 30 seconds to give users time to read
+const autoCloseTimeout = setTimeout(closeModal, 30000);
+
+// Clear timeout if user interacts
+overlay.addEventListener('click', () => {
+    clearTimeout(autoCloseTimeout);
+});

535-539: TODO comment indicates incomplete implementation

The handleSendMessage method has a TODO comment but no implementation.

The chat message handling is not implemented. Would you like me to help implement the API call for chat functionality or create an issue to track this task?

Comment on lines +123 to +136
public function generate($prompt, $provider = 'predefined') {
switch ($provider) {
case 'predefined':
$service = new PredefinedProvider();
break;
case 'openai':
$service = new OpenaiProvider();
break;
default:
throw new \Exception('Unknown provider');
}

return $service->generateForm($prompt);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistent provider method names (generateForm vs generate_form).

Examples use both generateForm() and generate_form(). This will lead to fatal errors when wiring providers via FormGenerator. Standardize on one name across all snippets and usages.

Apply a consistent rename:

- return $service->generateForm($prompt);
+ return $service->generate_form($prompt);
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function generate($prompt, $provider = 'predefined') {
switch ($provider) {
case 'predefined':
$service = new PredefinedProvider();
break;
case 'openai':
$service = new OpenaiProvider();
break;
default:
throw new \Exception('Unknown provider');
}
return $service->generateForm($prompt);
}
public function generate($prompt, $provider = 'predefined') {
switch ($provider) {
case 'predefined':
$service = new PredefinedProvider();
break;
case 'openai':
$service = new OpenaiProvider();
break;
default:
throw new \Exception('Unknown provider');
}
return $service->generate_form($prompt);
}
πŸ€– Prompt for AI Agents
In AI_FORM_BUILDER_IMPLEMENTATION_PLAN.md around lines 123 to 136, the provider
implementations and callers mix generateForm() and generate_form(), causing
fatal method-not-found errors; standardize on a single method name (use
generateForm) by renaming generate_form() to generateForm() in all provider
classes, update any interfaces/abstract classes to declare generateForm(), and
update all call sites (including FormGenerator and examples) to invoke
generateForm(); run a quick grep across the repo to ensure no remaining
generate_form references and run tests.

Comment on lines +315 to +387
class AI_Response_Parser {
public function parse_response($provider, $response) {
$method = 'parse_' . $provider . '_response';

if (method_exists($this, $method)) {
return $this->$method($response);
}

return $this->parse_generic_response($response);
}

private function parse_openai_response($response) {
$data = json_decode($response, true);

if (isset($data['choices'][0]['message']['content'])) {
$content = $data['choices'][0]['message']['content'];
return $this->extract_json_from_content($content);
}

throw new Exception('Invalid OpenAI response format');
}

private function parse_anthropic_response($response) {
$data = json_decode($response, true);

if (isset($data['content'][0]['text'])) {
$content = $data['content'][0]['text'];
return $this->extract_json_from_content($content);
}

throw new Exception('Invalid Anthropic response format');
}

private function parse_google_response($response) {
$data = json_decode($response, true);

if (isset($data['candidates'][0]['content']['parts'][0]['text'])) {
$content = $data['candidates'][0]['content']['parts'][0]['text'];
return $this->extract_json_from_content($content);
}

throw new Exception('Invalid Google response format');
}

private function parse_cohere_response($response) {
$data = json_decode($response, true);

if (isset($data['text'])) {
$content = $data['text'];
return $this->extract_json_from_content($content);
}

throw new Exception('Invalid Cohere response format');
}

private function extract_json_from_content($content) {
// Try to extract JSON from the content
if (preg_match('/\{.*\}/s', $content, $matches)) {
$json = json_decode($matches[0], true);
if (json_last_error() === JSON_ERROR_NONE) {
return $json;
}
}

// Try to parse the entire content as JSON
$json = json_decode($content, true);
if (json_last_error() === JSON_ERROR_NONE) {
return $json;
}

throw new Exception('Could not extract valid JSON from response');
}
}
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

JSON extraction via regex is brittle.

preg_match('/\{.*\}/s', $content, ...) can over-capture nested braces and fail on code fences. Prefer streaming parse with first valid-JSON slice or require tool-JSON via provider β€œjson mode”.

Example approach:

- if (preg_match('/\{.*\}/s', $content, $matches)) {
-     $json = json_decode($matches[0], true);
-     ...
- }
+ $candidates = [$content];
+ if (preg_match_all('/```json\s*(\{.*?\})\s*```/s', $content, $m)) {
+     $candidates = array_merge($m[1], $candidates);
+ }
+ foreach ($candidates as $c) {
+     $json = json_decode(trim($c), true);
+     if (json_last_error() === JSON_ERROR_NONE) { return $json; }
+ }
πŸ€– Prompt for AI Agents
In AI_FORM_BUILDER_IMPLEMENTATION_PLAN.md around lines 315-387, the
extract_json_from_content implementation uses a single greedy regex
(preg_match('/\{.*\}/s',...)) which is brittle and will over-capture nested
braces or miss JSON inside code fences; replace it with a robust extraction
routine that: 1) first scans for explicit JSON code fences and ```json blocks
(collect all matches), 2) then collects non-greedy brace-delimited candidates
(e.g. small /{.*?}/s matches), and 3) as a fallback perform a balanced-brace
scanner that iterates from each '{' and advances a brace counter to find the
matching closing '}' to produce exact slices; iterate over all collected
candidates, trim and json_decode each, returning the first one where
json_last_error() === JSON_ERROR_NONE, and only if none succeed throw the
exception.

Comment on lines +484 to +495
private function addWPUFDefaults($form_data) {
// Add WPUF-specific settings
$form_data['form_settings'] = array_merge([
'form_title' => $form_data['form_title'] ?? 'AI Generated Form',
'submit_text' => 'Submit',
'success_message' => 'Form submitted successfully!',
'form_template' => 'default'
], $form_data['form_settings'] ?? []);

return $form_data;
}
}
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Normalizer: field IDs and template mapping.

  • uniqid() yields string IDs; WPUF core often expects numeric keys for field arrays. Consider using incremental integers.
  • Some mappings (email_address, image_upload, taxonomy) need input_type/template pairs to render correctly. Extend the mapping to set both when required.
- if (!isset($field['id'])) { $field['id'] = uniqid(); }
+ static $nextId = 1;
+ if (!isset($field['id'])) { $field['id'] = $nextId++; }

- if (isset($type_mapping[$field['type']])) { $field['template'] = $type_mapping[$field['type']]; }
+ if (isset($type_mapping[$field['type']])) {
+   $field['template'] = $type_mapping[$field['type']];
+   // ensure input_type for complex templates
+   if (!isset($field['input_type'])) {
+     $field['input_type'] = $field['type'];
+   }
+ }

Also applies to: 486-491

πŸ€– Prompt for AI Agents
In AI_FORM_BUILDER_IMPLEMENTATION_PLAN.md around lines 484 to 495, the
normalizer uses uniqid() string IDs and incomplete mappings which WPUF expects
numeric field keys and paired input_type/template values; replace uniqid() usage
with an incremental integer counter for field array keys (ensure uniqueness
within the form by starting from 1 or max existing key +1), and extend the
mapping logic so that fields like email_address, image_upload and taxonomy set
both the input_type and the corresponding template (or form_template) entries so
WPUF can render them properly.

Comment on lines +881 to +882
throw new Exception('Unknown 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

Qualify Exception in namespaced code.

Within namespace WeDevs\Wpuf\AI;, throw new Exception(...) resolves to WeDevs\Wpuf\AI\Exception. Use \Exception (or import) to avoid runtime errors.

- throw new Exception('Unknown provider: ' . $this->current_provider);
+ throw new \Exception('Unknown provider: ' . $this->current_provider);

- throw new Exception('API request failed: ' . $response->get_error_message());
+ throw new \Exception('API request failed: ' . $response->get_error_message());

- throw new Exception('API returned error ' . $status_code . ': ' . $body);
+ throw new \Exception('API returned error ' . $status_code . ': ' . $body);

Also applies to: 949-951, 957-960

πŸ€– Prompt for AI Agents
In AI_FORM_BUILDER_IMPLEMENTATION_PLAN.md around lines 881-882 (and similarly at
949-951 and 957-960), the code throws Exception unqualified inside namespace
WeDevs\Wpuf\AI which resolves to WeDevs\Wpuf\AI\Exception and can cause runtime
errors; update each throw to reference the global Exception class by using
\Exception (or add a use statement / import for Exception) so the thrown
exception resolves correctly.

Comment on lines +1044 to +1189
class WPUF_AI_REST_Controller {

private $namespace = 'wpuf/v1';
private $rest_base = 'ai-form-builder';

public function __construct() {
add_action('rest_api_init', [$this, 'register_routes']);
}

public function register_routes() {
// Generate form endpoint
register_rest_route($this->namespace, '/' . $this->rest_base . '/generate', [
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'generate_form'],
'permission_callback' => [$this, 'check_permission'],
'args' => [
'prompt' => [
'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field'
],
'provider' => [
'required' => false,
'type' => 'string',
'default' => 'predefined',
'enum' => ['predefined', 'openai', 'anthropic', 'google']
]
]
]);

// Test connection endpoint
register_rest_route($this->namespace, '/' . $this->rest_base . '/test', [
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'test_connection'],
'permission_callback' => [$this, 'check_permission']
]);
}

public function generate_form(WP_REST_Request $request) {
$prompt = $request->get_param('prompt');
$provider = $request->get_param('provider');

try {
// Get settings
$settings = get_option('wpuf_ai_settings', []);

// Use predefined provider if no API key or explicitly selected
if ($provider === 'predefined' || empty($settings['api_key'])) {
$predefined = new Predefined_AI_Provider();
$result = $predefined->generate_form($prompt);
} else {
// Use selected provider with WordPress HTTP API
switch ($provider) {
case 'openai':
$service = new WPUF_OpenAI_Simple($settings['api_key']);
$result = $service->generate_form($prompt, $settings['model'] ?? 'gpt-3.5-turbo');
break;

case 'anthropic':
$service = new WPUF_Anthropic_Simple($settings['api_key']);
$result = $service->generate_form($prompt, $settings['model'] ?? 'claude-3-haiku-20240307');
break;

default:
throw new Exception('Unsupported provider: ' . $provider);
}
}

// Check for errors
if (isset($result['error']) && $result['error']) {
return new WP_Error(
'generation_failed',
$result['message'] ?? 'Form generation failed',
['status' => 400]
);
}

// Return successful response
return new WP_REST_Response([
'success' => true,
'data' => $result
], 200);

} catch (Exception $e) {
return new WP_Error(
'generation_error',
$e->getMessage(),
['status' => 500]
);
}
}

public function test_connection(WP_REST_Request $request) {
$settings = get_option('wpuf_ai_settings', []);
$provider = $settings['provider'] ?? 'predefined';

if ($provider === 'predefined') {
return new WP_REST_Response([
'success' => true,
'provider' => 'predefined',
'message' => 'Predefined provider is always available'
], 200);
}

// Test actual API connection with minimal request
try {
$test_prompt = 'Say "connection successful"';

switch ($provider) {
case 'openai':
$service = new WPUF_OpenAI_Simple($settings['api_key']);
$service->generate_form($test_prompt, 'gpt-3.5-turbo');
break;

case 'anthropic':
$service = new WPUF_Anthropic_Simple($settings['api_key']);
$service->generate_form($test_prompt, 'claude-3-haiku-20240307');
break;
}

return new WP_REST_Response([
'success' => true,
'provider' => $provider,
'message' => 'Connection successful'
], 200);

} catch (Exception $e) {
return new WP_Error(
'connection_failed',
$e->getMessage(),
['status' => 500]
);
}
}

public function check_permission() {
// Check if user can manage options (admin capability)
return current_user_can('manage_options');
}
}

// Initialize REST controller
add_action('init', function() {
new WPUF_AI_REST_Controller();
});
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

REST controller class name drift vs provider classes.

This block references Predefined_AI_Provider, WPUF_OpenAI_Simple, WPUF_Anthropic_Simple but earlier snippets define PredefinedProvider, OpenaiProvider, AnthropicProvider. Harmonize names to match actual classes to prevent copy/paste implementation mistakes.

πŸ€– Prompt for AI Agents
AI_FORM_BUILDER_IMPLEMENTATION_PLAN.md lines 1044-1189: The REST controller uses
incorrect class names (Predefined_AI_Provider, WPUF_OpenAI_Simple,
WPUF_Anthropic_Simple) that don’t match the actual provider classes
(PredefinedProvider, OpenaiProvider, AnthropicProvider); update the controller
to instantiate the real class names used elsewhere and adjust any constructor
arguments/method names to match those provider implementations (e.g., replace
Predefined_AI_Provider with PredefinedProvider, WPUF_OpenAI_Simple with
OpenaiProvider, and WPUF_Anthropic_Simple with AnthropicProvider), ensuring
generate_form/test method signatures are called with the correct parameters.

Comment on lines +149 to +153
// 1. Initial form creation
echo "User: Create a contact form\n";
$result1 = $generator->generate_form('Create a contact form', ['conversation_context' => []]);
echo "AI: Created form with " . count($result1['fields'] ?? []) . " fields\n\n";

Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Also normalize field count in the flow example

Keep the conversation example consistent with the spec.

 echo "User: Create a contact form\n";
 $result1 = $generator->generate_form('Create a contact form', ['conversation_context' => []]);
-echo "AI: Created form with " . count($result1['fields'] ?? []) . " fields\n\n";
+$fields1 = $result1['wpuf_fields'] ?? $result1['fields'] ?? [];
+echo "AI: Created form with " . count($fields1) . " fields\n\n";
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 1. Initial form creation
echo "User: Create a contact form\n";
$result1 = $generator->generate_form('Create a contact form', ['conversation_context' => []]);
echo "AI: Created form with " . count($result1['fields'] ?? []) . " fields\n\n";
// 1. Initial form creation
echo "User: Create a contact form\n";
$result1 = $generator->generate_form('Create a contact form', ['conversation_context' => []]);
$fields1 = $result1['wpuf_fields'] ?? $result1['fields'] ?? [];
echo "AI: Created form with " . count($fields1) . " fields\n\n";
πŸ€– Prompt for AI Agents
In test-ai-context.php around lines 149 to 153, the example prints the raw field
count using count($result1['fields'] ?? []), which is inconsistent with the spec
that uses normalized field lists; update the printed count to use the normalized
fields if present (for example count($result1['fields_normalized'] ??
$result1['fields'] ?? [])) so the conversation example always reflects the
spec-normalized field count.

Comment on lines +6 to +7
$API_KEY = 'YOUR_GOOGLE_GEMINI_API_KEY_HERE';

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Do not hardcode API keys; restrict execution

Avoid committing API key placeholders and block web access. Load from env and require CLI.

-$API_KEY = 'YOUR_GOOGLE_GEMINI_API_KEY_HERE';
+if (php_sapi_name() !== 'cli') {
+    header('HTTP/1.1 403 Forbidden');
+    exit;
+}
+$API_KEY = getenv('GOOGLE_API_KEY') ?: '';
+if ($API_KEY === '') {
+    fwrite(STDERR, "GOOGLE_API_KEY not set in environment.\n");
+    exit(1);
+}
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$API_KEY = 'YOUR_GOOGLE_GEMINI_API_KEY_HERE';
// Restrict to CLI only
if (php_sapi_name() !== 'cli') {
header('HTTP/1.1 403 Forbidden');
exit;
}
// Load API key from environment
$API_KEY = getenv('GOOGLE_API_KEY') ?: '';
if ($API_KEY === '') {
fwrite(STDERR, "GOOGLE_API_KEY not set in environment.\n");
exit(1);
}
πŸ€– Prompt for AI Agents
In test-google-context.php around lines 6-7, remove the hardcoded $API_KEY and
instead load the key from an environment variable (e.g. getenv or $_ENV like
GOOGLE_GEMINI_API_KEY), fail fast with a clear message and non-zero exit if the
env var is empty, and immediately prevent web execution by checking runtime
(php_sapi_name() or PHP_SAPI) and exiting unless running via CLI; ensure no
default placeholder value is committed and return a non-zero status when the key
is missing.

Comment on lines +114 to +124
} else if (isset($result['form_title'])) {
echo "Response: FORM CREATED\n";
echo "Title: " . $result['form_title'] . "\n";
echo "Fields: " . count($result['fields'] ?? []) . "\n";

// Show field types
if (isset($result['fields'])) {
foreach ($result['fields'] as $field) {
echo " - " . $field['label'] . " (" . $field['type'] . ")\n";
}
}
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Print fields from wpuf_fields (or normalize) per spec

Align output with the system prompt by preferring wpuf_fields and falling back to fields.

-    } else if (isset($result['form_title'])) {
+    } else if (isset($result['form_title'])) {
         echo "Response: FORM CREATED\n";
         echo "Title: " . $result['form_title'] . "\n";
-        echo "Fields: " . count($result['fields'] ?? []) . "\n";
+        $fields = $result['wpuf_fields'] ?? $result['fields'] ?? [];
+        echo "Fields: " . count($fields) . "\n";
         
         // Show field types
-        if (isset($result['fields'])) {
-            foreach ($result['fields'] as $field) {
-                echo "  - " . $field['label'] . " (" . $field['type'] . ")\n";
+        if (!empty($fields)) {
+            foreach ($fields as $field) {
+                $label = $field['label'] ?? ($field['name'] ?? 'field');
+                $type  = $field['input_type'] ?? ($field['type'] ?? 'unknown');
+                echo "  - " . $label . " (" . $type . ")\n";
             }
         }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (isset($result['form_title'])) {
echo "Response: FORM CREATED\n";
echo "Title: " . $result['form_title'] . "\n";
echo "Fields: " . count($result['fields'] ?? []) . "\n";
// Show field types
if (isset($result['fields'])) {
foreach ($result['fields'] as $field) {
echo " - " . $field['label'] . " (" . $field['type'] . ")\n";
}
}
} else if (isset($result['form_title'])) {
echo "Response: FORM CREATED\n";
echo "Title: " . $result['form_title'] . "\n";
$fields = $result['wpuf_fields'] ?? $result['fields'] ?? [];
echo "Fields: " . count($fields) . "\n";
// Show field types
if (!empty($fields)) {
foreach ($fields as $field) {
$label = $field['label'] ?? ($field['name'] ?? 'field');
$type = $field['input_type'] ?? ($field['type'] ?? 'unknown');
echo " - " . $label . " (" . $type . ")\n";
}
}
πŸ€– Prompt for AI Agents
In test-google-context.php around lines 114 to 124, the output always uses
$result['fields'] but the system requires preferring wpuf_fields and falling
back to fields; update the logic to choose $fields = $result['wpuf_fields'] ??
$result['fields'] ?? [] for the count and iteration, print "Fields: " .
count($fields), and then loop over $fields to echo each label and type (handling
missing keys defensively), ensuring the code uses wpuf_fields when present and
only falls back to fields.

Comment on lines +59 to +61
echo "Testing Google Gemini API...\n";
echo "Endpoint: $endpoint\n\n";

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Do not print endpoint with API key (secret leakage).

echo "Endpoint: $endpoint" exposes the API key in logs/CI. Mask or omit it.

-echo "Testing Google Gemini API...\n";
-echo "Endpoint: $endpoint\n\n";
+echo "Testing Google Gemini API...\n";
+echo "Endpoint: https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key=****\n\n";
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "Testing Google Gemini API...\n";
echo "Endpoint: $endpoint\n\n";
echo "Testing Google Gemini API...\n";
echo "Endpoint: https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key=****\n\n";
πŸ€– Prompt for AI Agents
In test-google-gemini.php around lines 59 to 61, the code prints the full
endpoint which contains the API key causing secret leakage; remove or mask the
API key before logging by either omitting the endpoint entirely or printing a
redacted version (e.g., only hostname or endpoint path and replace
query/credential parts with "***"), ensure any logging uses the sanitized value
so secrets are not exposed in stdout/CI logs.

Comment on lines +62 to +69
// Make the request
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request_body));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Harden cURL request: timeouts, UA, and JSON headers.

Avoid hangs and improve diagnostics.

 $ch = curl_init($endpoint);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request_body));
-curl_setopt($ch, CURLOPT_HTTPHEADER, [
-    'Content-Type: application/json'
-]);
+curl_setopt($ch, CURLOPT_HTTPHEADER, [
+    'Content-Type: application/json',
+    'Accept: application/json',
+    'User-Agent: WPUF-AI-Gemini-Test/1.0'
+]);
+curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
+curl_setopt($ch, CURLOPT_TIMEOUT, 30);
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Make the request
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request_body));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
// Make the request
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request_body));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Accept: application/json',
'User-Agent: WPUF-AI-Gemini-Test/1.0'
]);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
πŸ€– Prompt for AI Agents
In test-google-gemini.php around lines 62 to 69, the cURL request lacks
timeouts, a User-Agent and richer JSON/diagnostic headers which can cause hangs
and poor debugging; update the curl options to set a connect timeout and overall
timeout (e.g. CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT), ensure SSL
verification remains enabled (CURLOPT_SSL_VERIFYPEER), add a User-Agent header,
include Accept: application/json and Content-Length headers alongside
Content-Type, and consider enabling CURLOPT_FAILONERROR so HTTP errors return
and can be logged; apply these header and option changes when initializing $ch.

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.

Review continued from previous batch...

Comment on lines +214 to +267
const style = document.createElement('style');
style.id = 'wpuf-ai-error-styles';
style.textContent = `
.wpuf-ai-error-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999999;
display: flex;
align-items: center;
justify-content: center;
}
.wpuf-ai-error-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.wpuf-ai-error-content {
position: relative;
background: white;
padding: 30px;
border-radius: 8px;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.wpuf-ai-error-title {
color: #dc3545;
margin: 0 0 15px 0;
font-size: 20px;
}
.wpuf-ai-error-message {
color: #333;
margin: 0 0 10px 0;
line-height: 1.5;
}
.wpuf-ai-error-hint {
color: #666;
font-style: italic;
margin: 10px 0 20px 0;
padding: 10px;
background: #f8f9fa;
border-left: 3px solid #007cba;
}
.wpuf-ai-error-close {
margin-top: 15px;
}
`;
document.head.appendChild(style);
}
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Dynamic style injection can cause memory leaks

The error modal creates style elements dynamically but never removes them, potentially causing memory leaks with repeated errors.

Check if styles already exist before adding:

 // Add styles if not already present
 if (!document.getElementById('wpuf-ai-error-styles')) {
     const style = document.createElement('style');
     style.id = 'wpuf-ai-error-styles';
     style.textContent = `
         .wpuf-ai-error-modal {
             position: fixed;
             top: 0;
             left: 0;
             right: 0;
             bottom: 0;
             z-index: 999999;
             display: flex;
             align-items: center;
             justify-content: center;
         }
         /* ... rest of styles ... */
     `;
     document.head.appendChild(style);
+    
+    // Clean up styles when component is destroyed
+    this.$once('hook:beforeDestroy', () => {
+        const styleEl = document.getElementById('wpuf-ai-error-styles');
+        if (styleEl) {
+            styleEl.remove();
+        }
+    });
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const style = document.createElement('style');
style.id = 'wpuf-ai-error-styles';
style.textContent = `
.wpuf-ai-error-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999999;
display: flex;
align-items: center;
justify-content: center;
}
.wpuf-ai-error-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.wpuf-ai-error-content {
position: relative;
background: white;
padding: 30px;
border-radius: 8px;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.wpuf-ai-error-title {
color: #dc3545;
margin: 0 0 15px 0;
font-size: 20px;
}
.wpuf-ai-error-message {
color: #333;
margin: 0 0 10px 0;
line-height: 1.5;
}
.wpuf-ai-error-hint {
color: #666;
font-style: italic;
margin: 10px 0 20px 0;
padding: 10px;
background: #f8f9fa;
border-left: 3px solid #007cba;
}
.wpuf-ai-error-close {
margin-top: 15px;
}
`;
document.head.appendChild(style);
}
// Add styles if not already present
if (!document.getElementById('wpuf-ai-error-styles')) {
const style = document.createElement('style');
style.id = 'wpuf-ai-error-styles';
style.textContent = `
.wpuf-ai-error-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999999;
display: flex;
align-items: center;
justify-content: center;
}
.wpuf-ai-error-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.wpuf-ai-error-content {
position: relative;
background: white;
padding: 30px;
border-radius: 8px;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.wpuf-ai-error-title {
color: #dc3545;
margin: 0 0 15px 0;
font-size: 20px;
}
.wpuf-ai-error-message {
color: #333;
margin: 0 0 10px 0;
line-height: 1.5;
}
.wpuf-ai-error-hint {
color: #666;
font-style: italic;
margin: 10px 0 20px 0;
padding: 10px;
background: #f8f9fa;
border-left: 3px solid #007cba;
}
.wpuf-ai-error-close {
margin-top: 15px;
}
`;
document.head.appendChild(style);
// Clean up styles when component is destroyed
this.$once('hook:beforeDestroy', () => {
const styleEl = document.getElementById('wpuf-ai-error-styles');
if (styleEl) {
styleEl.remove();
}
});
}
πŸ€– Prompt for AI Agents
In assets/js/components/AIFormBuilder.vue around lines 214 to 267, the component
always injects a <style> element with id 'wpuf-ai-error-styles' when showing the
error modal, which can leak memory if done repeatedly; modify the logic to first
check document.getElementById('wpuf-ai-error-styles') and only create/append the
style element if it doesn't already exist, and ensure that when the modal is
removed or the component is unmounted you also remove the style element (or
leave it if intended to persist) to avoid orphaned elements; additionally,
consolidate style injection into a single init/cleanup path (e.g., create once
on mount and remove on unmount) rather than recreating on each error.

Comment on lines +43 to +46
*/
public function __construct() {
$this->form_generator = new FormGenerator();
add_action('rest_api_init', [$this, 'register_routes']);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

RestController couples REST route registration to constructor

Registering REST routes in the constructor can cause issues if the class is instantiated before rest_api_init action fires.

Move the hook registration to a separate init method:

 public function __construct() {
     $this->form_generator = new FormGenerator();
-    add_action('rest_api_init', [$this, 'register_routes']);
+}
+
+/**
+ * Initialize REST routes
+ */
+public function init() {
+    add_action('rest_api_init', [$this, 'register_routes']);
 }

And update AI_Manager.php to call init():

 private function init_classes() {
     // Initialize Form Generator
     $this->form_generator = new FormGenerator();
     
     // Initialize REST Controller
     $this->rest_controller = new RestController();
+    $this->rest_controller->init();
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*/
public function __construct() {
$this->form_generator = new FormGenerator();
add_action('rest_api_init', [$this, 'register_routes']);
/**
* …
*/
public function __construct() {
$this->form_generator = new FormGenerator();
}
/**
* Initialize REST routes
*/
public function init() {
add_action('rest_api_init', [$this, 'register_routes']);
}
πŸ€– Prompt for AI Agents
In includes/AI/RestController.php around lines 43-46, the REST route
registration is being hooked in the constructor which can run before
rest_api_init fires; remove the add_action('rest_api_init', [$this,
'register_routes']) call from the constructor, create a new public init() method
that calls add_action('rest_api_init', [$this, 'register_routes']), keep
instantiating FormGenerator in the constructor, and then update AI_Manager.php
to call $restController->init() (or equivalent) after constructing the
RestController so route registration happens during or after rest_api_init.

'provider' => [
'required' => true,
'type' => 'string',
'enum' => ['predefined', 'openai', 'anthropic']
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing 'google' provider in settings endpoint enum

The generate endpoint includes 'google' as an allowed provider (line 79), but the settings endpoint doesn't (line 121).

Add the missing provider for consistency:

 'provider' => [
     'required' => true,
     'type' => 'string',
-    'enum' => ['predefined', 'openai', 'anthropic']
+    'enum' => ['predefined', 'openai', 'anthropic', 'google']
 ],
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'enum' => ['predefined', 'openai', 'anthropic']
'provider' => [
'required' => true,
'type' => 'string',
'enum' => ['predefined', 'openai', 'anthropic', 'google']
],
πŸ€– Prompt for AI Agents
In includes/AI/RestController.php around line 121, the settings endpoint enum
omits the 'google' provider while the generate endpoint allows it; update the
enum array to include 'google' (i.e., add 'google' to the list
['predefined','openai','anthropic']) so both endpoints use the same allowed
providers.

Comment on lines +415 to +429
$max_requests = 50; // 50 requests per hour

if ($current_count === false) {
// First request in this hour
set_transient($rate_limit_key, 1, HOUR_IN_SECONDS);
return true;
}

if ($current_count >= $max_requests) {
return false;
}

// Increment counter
set_transient($rate_limit_key, $current_count + 1, HOUR_IN_SECONDS);
return true;
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Rate limiting uses transients which may not be reliable

Using transients for rate limiting can be unreliable if an object cache is misconfigured or if transients are stored in memory.

Consider using user meta for more reliable rate limiting:

 private function check_rate_limit() {
     $user_id = get_current_user_id();
-    $rate_limit_key = 'wpuf_ai_rate_limit_' . $user_id;
+    $current_hour = date('Y-m-d-H');
+    $rate_limit_meta_key = 'wpuf_ai_rate_limit';
     
-    $current_count = get_transient($rate_limit_key);
+    $rate_data = get_user_meta($user_id, $rate_limit_meta_key, true);
+    
+    if (!is_array($rate_data) || $rate_data['hour'] !== $current_hour) {
+        // New hour, reset counter
+        $rate_data = ['hour' => $current_hour, 'count' => 0];
+    }
+    
     $max_requests = 50; // 50 requests per hour
     
-    if ($current_count === false) {
-        // First request in this hour
-        set_transient($rate_limit_key, 1, HOUR_IN_SECONDS);
-        return true;
-    }
-    
-    if ($current_count >= $max_requests) {
+    if ($rate_data['count'] >= $max_requests) {
         return false;
     }
     
-    // Increment counter
-    set_transient($rate_limit_key, $current_count + 1, HOUR_IN_SECONDS);
+    $rate_data['count']++;
+    update_user_meta($user_id, $rate_limit_meta_key, $rate_data);
     return true;
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$max_requests = 50; // 50 requests per hour
if ($current_count === false) {
// First request in this hour
set_transient($rate_limit_key, 1, HOUR_IN_SECONDS);
return true;
}
if ($current_count >= $max_requests) {
return false;
}
// Increment counter
set_transient($rate_limit_key, $current_count + 1, HOUR_IN_SECONDS);
return true;
private function check_rate_limit() {
$user_id = get_current_user_id();
$current_hour = date('Y-m-d-H');
$rate_limit_meta_key = 'wpuf_ai_rate_limit';
$rate_data = get_user_meta($user_id, $rate_limit_meta_key, true);
if (! is_array($rate_data) || $rate_data['hour'] !== $current_hour) {
// New hour, reset counter
$rate_data = [
'hour' => $current_hour,
'count' => 0,
];
}
$max_requests = 50; // 50 requests per hour
if ($rate_data['count'] >= $max_requests) {
return false;
}
$rate_data['count']++;
update_user_meta($user_id, $rate_limit_meta_key, $rate_data);
return true;
}

'post_status' => 'publish',
'post_parent' => $form_id,
'menu_order' => $order,
'post_content' => serialize($field) // WPUF stores field data as serialized content
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Using serialize() for data storage is a security risk

WordPress Core discourages using serialize() for storing data due to potential object injection vulnerabilities.

Use JSON encoding instead:

 'post_parent' => $form_id,
 'menu_order' => $order,
-'post_content' => serialize($field) // WPUF stores field data as serialized content
+'post_content' => wp_json_encode($field) // Store as JSON for security

You'll also need to update the retrieval logic to decode JSON instead of unserializing.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'post_content' => serialize($field) // WPUF stores field data as serialized content
'post_parent' => $form_id,
'menu_order' => $order,
'post_content' => wp_json_encode( $field ) // Store as JSON for security
πŸ€– Prompt for AI Agents
In includes/AI/RestController.php around line 489, the code currently uses
serialize($field) to store field data which is discouraged for security; replace
serialize() with json_encode($field, JSON_UNESCAPED_UNICODE) when saving and
ensure the value is stored as a JSON string, and update all retrieval sites to
use json_decode($value, true) (or false when objects are required) instead of
unserialize(), validating/typing the decoded data before use to avoid injection
issues.

Comment on lines +782 to +783
return 'wpuf_ai_session_' . time() . '_' . wp_generate_uuid4();
}
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Session ID generation uses wp_generate_uuid4 which may not exist

The wp_generate_uuid4() function was introduced in WordPress 4.7 but might not be available in all environments.

Add a fallback for older WordPress versions:

 private function generate_session_id() {
-    return 'wpuf_ai_session_' . time() . '_' . wp_generate_uuid4();
+    $unique_id = function_exists('wp_generate_uuid4') 
+        ? wp_generate_uuid4() 
+        : wp_generate_password(32, false);
+    return 'wpuf_ai_session_' . time() . '_' . $unique_id;
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return 'wpuf_ai_session_' . time() . '_' . wp_generate_uuid4();
}
private function generate_session_id() {
$unique_id = function_exists('wp_generate_uuid4')
? wp_generate_uuid4()
: wp_generate_password(32, false);
return 'wpuf_ai_session_' . time() . '_' . $unique_id;
}
πŸ€– Prompt for AI Agents
In includes/AI/RestController.php around lines 782-783, the session ID
generation calls wp_generate_uuid4() which may not exist on older WP versions;
update the function to check function_exists('wp_generate_uuid4') and use it
when available, otherwise generate a safe UUID4 fallback (e.g., build a
version-4 UUID from random_bytes/openssl_random_pseudo_bytes and proper hex
formatting) or reliably fallback to uniqid combined with secure random data and
hashing to ensure uniqueness and entropy; return the same 'wpuf_ai_session_' .
time() . '_' . <fallback> value so callers are unchanged.

'post_status' => 'publish',
'post_parent' => $form_id,
'menu_order' => $order,
'post_content' => serialize($field)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Using serialize() again for field storage

Same serialization issue as mentioned earlier.

Use JSON encoding:

 'post_parent' => $form_id,
 'menu_order' => $order,
-'post_content' => serialize($field)
+'post_content' => wp_json_encode($field)
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'post_content' => serialize($field)
'post_parent' => $form_id,
'menu_order' => $order,
'post_content' => wp_json_encode($field)
πŸ€– Prompt for AI Agents
In includes/AI/RestController.php around line 903, post_content is being set
with serialize($field) which repeats the earlier serialization problem; replace
serialize($field) with a JSON encoder (preferably wp_json_encode($field) or
json_encode($field)) so the field is stored as JSON, and ensure any code that
reads this value uses json_decode (or json_decode(..., true)) instead of
unserialize.

Comment on lines +972 to +975
private function should_be_meta($field_name) {
$post_fields = ['post_title', 'post_content', 'post_excerpt', 'post_tags', 'post_category'];
return !in_array($field_name, $post_fields);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unused private method

The should_be_meta method is never called.

Remove the unused method:

-/**
- * Determine if field should be meta
- *
- * @param string $field_name
- * @return bool
- */
-private function should_be_meta($field_name) {
-    $post_fields = ['post_title', 'post_content', 'post_excerpt', 'post_tags', 'post_category'];
-    return !in_array($field_name, $post_fields);
-}
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private function should_be_meta($field_name) {
$post_fields = ['post_title', 'post_content', 'post_excerpt', 'post_tags', 'post_category'];
return !in_array($field_name, $post_fields);
}
// (The unused should_be_meta method has been removed.)
🧰 Tools
πŸͺ› PHPMD (2.15.0)

972-975: Avoid unused private methods such as 'should_be_meta'. (Unused Code Rules)

(UnusedPrivateMethod)

πŸ€– Prompt for AI Agents
In includes/AI/RestController.php around lines 972 to 975, the private method
should_be_meta(...) is unused; remove the entire private method declaration
(including its signature and body) from the file to clean up dead code, and run
a quick project-wide search to confirm there are no remaining references before
committing.

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: 14

♻️ Duplicate comments (6)
includes/AI/system-prompt.md (1)

16-20: Remove line comments from JSON example; they produce invalid JSON and bias models.

Keep examples strictly valid JSON; move guidance into surrounding text.

Apply this diff:

   "wpuf_fields": [
-    // REQUIRED: Array of complete field objects
-    // MUST include post_title as first field
-    // MUST include post_content as second field
-    // Then additional fields
   ],
includes/Admin.php (1)

45-46: Hook is registered but never fired; localization won’t run, breaking the AI UI.

enqueue_ai_form_builder_scripts won’t execute unless something calls do_action( 'wpuf_load_ai_form_builder_page' ). AI_Form_Handler directly enqueues scripts and never fires the hook, so wpufAIFormBuilder isn’t localized and the Vue app lacks config.

Apply this diff in includes/Admin/Forms/AI_Form_Handler.php to centralize enqueues and ensure localization:

@@ public function handle_ai_form_template() {
-    // Load CSS
-    wp_enqueue_style( 'wpuf-ai-form-builder' );
-    // Load scripts
-    wp_enqueue_script( 'wpuf-ai-form-builder' );
+    // Localize + enqueue via admin hook
+    do_action( 'wpuf_load_ai_form_builder_page' );
@@ public function handle_ai_form_generating() {
-    // Load CSS and scripts
-    wp_enqueue_style( 'wpuf-ai-form-builder' );
-    wp_enqueue_script( 'wpuf-ai-form-builder' );
+    do_action( 'wpuf_load_ai_form_builder_page' );
@@ public function handle_ai_form_success() {
-    // Load CSS and scripts
-    wp_enqueue_style( 'wpuf-ai-form-builder' );
-    wp_enqueue_script( 'wpuf-ai-form-builder' );
+    do_action( 'wpuf_load_ai_form_builder_page' );
includes/AI/wpuf-post-form-fields-reference.md (1)

83-85: Fix β€˜toc’ description: it’s Terms and Conditions, not Table of Contents.

Align with WPUF nomenclature to avoid mis-generation.

Apply:

-| `toc` | Table of Contents | Generate table of contents | ...
+| `toc` | Terms and Conditions | Consent checkbox with full terms text | ...
assets/js/components/AIFormBuilder.vue (2)

211-266: Clean up dynamically injected styles to prevent leaks.

The modal stylesheet persists across component lifecycles.

Add a lifecycle cleanup:

 export default {
   name: 'AIFormBuilder',
+  beforeDestroy() {
+      const styleEl = document.getElementById('wpuf-ai-error-styles');
+      if (styleEl) styleEl.remove();
+  },

Also applies to: 634-657


409-414: Generate a cryptographically strong session ID.

Use crypto.randomUUID()/getRandomValues; avoid predictable Math.random() + timestamp.

         getSessionId() {
             if (!this.sessionId) {
-                this.sessionId = 'wpuf_ai_session_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11);
+                const uuid = (window.crypto && window.crypto.randomUUID)
+                    ? window.crypto.randomUUID()
+                    : (window.crypto && window.crypto.getRandomValues)
+                        ? Array.from(window.crypto.getRandomValues(new Uint8Array(16))).map(b => b.toString(16).padStart(2,'0')).join('')
+                        : (Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2));
+                this.sessionId = 'wpuf_ai_session_' + uuid;
             }
             return this.sessionId;
         },
includes/functions/settings-options.php (1)

688-693: Mask and sanitize API key.

Render as password; sanitize on save to reduce accidental exposure.

             [
                 'name'    => 'ai_api_key',
                 'label'   => __( 'API Key', 'wp-user-frontend' ),
                 'desc'    => __( 'Enter your AI service API key. Keep this secure and never share it publicly.', 'wp-user-frontend' ),
-                'type'    => 'text',
+                'type'    => 'password',
                 'default' => '',
             ],

Also ensure sanitization via sanitize_text_field on save (settings API callback) and store with autoload = no.

🧹 Nitpick comments (7)
includes/AI/system-prompt.md (1)

112-112: Minor: avoid emphasis-as-heading.

Change the emphasized warning to a proper heading or normal paragraph to satisfy MD036.

includes/Admin.php (1)

176-212: Surface temperature/maxTokens from settings instead of hardcoding.

Respect saved admin settings; keep safe defaults.

Apply:

-                'temperature' => 0.7,
-                'maxTokens'  => 2000,
+                'temperature' => isset( $ai_settings['temperature'] ) ? (float) $ai_settings['temperature'] : 0.7,
+                'maxTokens'  => isset( $ai_settings['max_tokens'] ) ? (int) $ai_settings['max_tokens'] : 2000,
includes/AI/wpuf-post-form-fields-reference.md (1)

67-75: Add blank lines around tables to satisfy MD058.

Insert a blank line before and after each table block.

Also applies to: 79-87

Lib/AI/PredefinedProvider.php (2)

715-716: Avoid artificial 1s delay in production.

sleep(1) slows the UI. Gate behind a constant or WP_DEBUG.

-        sleep(1);
+        if (defined('WPUF_AI_DEV_DELAY') && WPUF_AI_DEV_DELAY) {
+            sleep(1);
+        }

803-805: Prefix global WP functions inside namespaces.

Use leading backslash for __(), get_option(), current_time() to avoid namespaced lookups.

Example:

-        $response['generated_at'] = current_time('mysql');
+        $response['generated_at'] = \current_time('mysql');
...
-                    $wpuf_field['first'] = __('- Select -', 'wp-user-frontend');
+                    $wpuf_field['first'] = \__('- Select -', 'wp-user-frontend');

Also applies to: 1024-1054, 1119-1166, 1202-1249

includes/AI/FormGenerator.php (1)

54-60: Align OpenAI model list with settings UI.

Settings include gpt-4o-2024-08-06 and o1-* but provider_configs doesn’t. Keep them in sync to avoid confusing UIs.

             'models' => [
                 'gpt-4o' => 'GPT-4o',
                 'gpt-4o-mini' => 'GPT-4o Mini',
+                'gpt-4o-2024-08-06' => 'GPT-4o (2024-08-06)',
                 'gpt-4-turbo' => 'GPT-4 Turbo',
                 'gpt-4' => 'GPT-4',
                 'gpt-3.5-turbo' => 'GPT-3.5 Turbo'
             ],

If supporting o1/o3 models, route them via the Responses API instead of Chat Completions to prevent 400s.

includes/functions/settings-options.php (1)

646-651: Offer a β€œPredefined (Testing)” provider in the UI.

It exists in the backend and is useful when no keys are set.

                 'options' => [
                     'openai'    => 'OpenAI',
                     'anthropic' => 'Anthropic',
-                    'google'    => 'Google',
+                    'google'    => 'Google',
+                    'predefined'=> __('Predefined (Testing)', 'wp-user-frontend'),
                 ],
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 040a0ad and 213f249.

β›” Files ignored due to path filters (1)
  • assets/js/ai-form-builder.min.js is excluded by !**/*.min.js
πŸ“’ Files selected for processing (7)
  • Lib/AI/PredefinedProvider.php (1 hunks)
  • assets/js/components/AIFormBuilder.vue (1 hunks)
  • includes/AI/FormGenerator.php (1 hunks)
  • includes/AI/system-prompt.md (1 hunks)
  • includes/AI/wpuf-post-form-fields-reference.md (1 hunks)
  • includes/Admin.php (4 hunks)
  • includes/functions/settings-options.php (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
includes/AI/FormGenerator.php (2)
Lib/AI/PredefinedProvider.php (2)
  • PredefinedProvider (13-1251)
  • generateForm (713-821)
includes/AI/RestController.php (4)
  • __construct (44-47)
  • generate_form (203-256)
  • get_providers (284-293)
  • test_connection (264-276)
includes/Admin.php (2)
includes/Admin/Forms/AI_Form_Handler.php (2)
  • AI_Form_Handler (12-140)
  • handle_ai_form_template (27-64)
Lib/WeDevs_Settings_API.php (1)
  • get_option (559-568)
πŸͺ› markdownlint-cli2 (0.17.2)
includes/AI/system-prompt.md

112-112: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

includes/AI/wpuf-post-form-fields-reference.md

67-67: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


79-79: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


190-190: Multiple headings with the same content

(MD024, no-duplicate-heading)

πŸ”‡ Additional comments (3)
includes/AI/wpuf-post-form-fields-reference.md (1)

58-62: All referenced field templates exist in code; no removals required.

includes/AI/FormGenerator.php (1)

219-243: OpenAI: consider Responses API for o1/o3 models and JSON Mode reliability.

Current implementation uses chat/completions with response_format. Some models require /v1/responses.

Would you like a follow-up patch that detects o1*/o3* model ids and switches endpoints/payload accordingly?

includes/functions/settings-options.php (1)

660-686: Model list/UI drifts from backend support.

UI lists o1-preview/o1-mini and gpt-4o-2024-08-06; provider_configs doesn’t. Align or filter options per provider.

I can add a small helper to expose FormGenerator::get_providers() to the settings page and render models dynamically; want me to prepare that?

Comment on lines +115 to +128
const response = await fetch(restUrl + 'wpuf/v1/ai-form-builder/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': nonce
},
body: JSON.stringify({
prompt: prompt,
session_id: this.getSessionId(),
provider: config.provider || 'predefined',
temperature: config.temperature || 0.7,
max_tokens: config.maxTokens || 2000
})
});
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Improve non-OK response handling: include server error body.

Currently throws with statusText only; WP errors return useful JSON. Parse JSON (fall back to text) for better UX.

-                if (!response.ok) {
-                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
-                }
+                if (!response.ok) {
+                    let detail = '';
+                    try {
+                        const errJson = await response.json();
+                        detail = errJson?.message || JSON.stringify(errJson);
+                    } catch (e) {
+                        detail = await response.text().catch(() => '');
+                    }
+                    throw new Error(`HTTP ${response.status}: ${detail || response.statusText}`);
+                }

Also applies to: 132-136, 166-177

πŸ€– Prompt for AI Agents
In assets/js/components/AIFormBuilder.vue around lines 115-128 (and similarly at
132-136 and 166-177), the fetch error handling only throws response.statusText
which discards helpful JSON error payloads from WP; update the logic to when
response.ok is false attempt to parse await response.json() inside a try/catch
(fall back to await response.text() if JSON parsing fails), then include that
parsed body (or text) in the thrown Error or rejected object so callers can show
the server error details; ensure you maintain the existing status/statusText
fields when constructing the error to preserve context.

Comment on lines +188 to +207
// Create a modal-like error display
const errorContainer = document.createElement('div');
errorContainer.className = 'wpuf-ai-error-modal';
errorContainer.innerHTML = `
<div class="wpuf-ai-error-overlay"></div>
<div class="wpuf-ai-error-content">
<h3 class="wpuf-ai-error-title">
${errorType === 'invalid_request' ? (i18n.invalidRequest || 'Invalid Request') : (i18n.errorTitle || 'Error')}
</h3>
<p class="wpuf-ai-error-message">${message}</p>
${errorType === 'invalid_request' ? `
<p class="wpuf-ai-error-hint">
${i18n.nonFormRequest || 'I can only help with form creation. Try: "Create a contact form"'}
</p>
` : ''}
<button class="wpuf-ai-error-close button button-primary">
${i18n.tryAgain || 'Try Again'}
</button>
</div>
`;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Block XSS: sanitize server-provided message before injecting innerHTML.

message is inserted via template literals into innerHTML. If the backend returns HTML, this is a stored/reflective XSS in wp-admin. Sanitize or set textContent instead.

Apply this diff to add a sanitizer and use it here:

+        sanitizeHtml(str) {
+            const div = document.createElement('div');
+            div.textContent = String(str ?? '');
+            return div.innerHTML;
+        },
...
         handleGenerationError(message, errorType = 'general') {
...
-            errorContainer.innerHTML = `
+            const safeMessage = this.sanitizeHtml(message);
+            errorContainer.innerHTML = `
...
-                    <p class="wpuf-ai-error-message">${message}</p>
+                    <p class="wpuf-ai-error-message">${safeMessage}</p>

Also applies to: 211-266

Comment on lines +286 to +305
if (result.form_data) {
this.generatedFormData = result.form_data;
this.formTitle = result.form_data.form_title || 'Generated Form';
// For preview, use fields (simplified), but store wpuf_fields for actual form creation
this.formFields = this.convertFieldsToPreview(result.form_data.fields || []);

setTimeout(() => {
this.handleGenerationComplete();

// Only show pro field warning if Pro is not active
if (!isProActive) {
setTimeout(() => {
this.showProFieldModal(result.message);
}, 500);
} else {
console.log('Pro is active, no need to show warning');
}
}, 1000);
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Undefined method convertFieldsToPreview causes runtime error; also breaks WPUF format.

This path calls an undefined method and converts away from WPUF format, but later code assumes WPUF fields.

Apply:

-                // For preview, use fields (simplified), but store wpuf_fields for actual form creation
-                this.formFields = this.convertFieldsToPreview(result.form_data.fields || []);
+                // Keep WPUF format to ensure create/edit works
+                this.formFields = result.form_data.wpuf_fields || result.form_data.fields || [];

Also applies to: 290-291

πŸ€– Prompt for AI Agents
In assets/js/components/AIFormBuilder.vue around lines 286 to 305 (also touching
290-291), the code calls an undefined convertFieldsToPreview and overwrites WPUF
field format causing runtime errors and later assumptions to break; replace that
call so generatedFormData preserves the original WPUF fields and only derive a
separate preview structure if needed. Specifically, remove the call to
convertFieldsToPreview and set this.formFields = result.form_data.wpuf_fields ||
result.form_data.fields || []; if a simplified preview is required, implement a
new helper (e.g., buildPreviewFields) that returns a copy derived from
wpuf_fields and assign it to a new property like this.previewFields, leaving
this.formFields as the WPUF-compatible array; ensure any references later in the
component use the correct property.

Comment on lines +340 to +372
modal.innerHTML = `
<div style="padding: 24px;">
<div style="display: flex; align-items: center; margin-bottom: 16px;">
<span style="color: #f39c12; font-size: 24px; margin-right: 12px;">⚑</span>
<h3 style="margin: 0; font-size: 20px; color: #333;">${i18n.proFieldWarning || 'Pro Feature Required'}</h3>
</div>
<p style="color: #666; margin-bottom: 20px; line-height: 1.5;">
${i18n.proFieldMessage || 'This field type requires WP User Frontend Pro. You can continue without it or upgrade to Pro for full functionality.'}
</p>
<div style="background: #f8f9fa; padding: 12px; border-radius: 4px; margin-bottom: 20px; font-size: 14px; color: #666;">
${message}
</div>
<div style="display: flex; gap: 12px; justify-content: flex-end;">
<button id="wpuf-continue-without-pro" style="
padding: 8px 16px;
border: 1px solid #ddd;
background: white;
color: #666;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
">${i18n.continueWithoutPro || 'Continue without Pro'}</button>
<a href="https://wedevs.com/wp-user-frontend-pro/pricing/" target="_blank" style="
padding: 8px 16px;
background: #0073aa;
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 14px;
">${i18n.upgradeToPro || 'Upgrade to Pro'}</a>
</div>
</div>
`;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Block XSS in Pro modal too.

The user-facing message is injected into innerHTML unsanitized.

Use the same sanitizer:

-            modal.innerHTML = `
+            const safeProMsg = this.sanitizeHtml(message);
+            modal.innerHTML = `
...
-                    <div style="background: #f8f9fa; padding: 12px; border-radius: 4px; margin-bottom: 20px; font-size: 14px; color: #666;">
-                        ${message}
-                    </div>
+                    <div style="background: #f8f9fa; padding: 12px; border-radius: 4px; margin-bottom: 20px; font-size: 14px; color: #666;">${safeProMsg}</div>

Also applies to: 374-393

πŸ€– Prompt for AI Agents
In assets/js/components/AIFormBuilder.vue around lines 340-372 (and likewise
apply the same change to lines 374-393), the modal HTML builds innerHTML with
unsanitized user-facing strings (e.g., message and i18n values) which allows
XSS; sanitize these values using the same project sanitizer function before
interpolation (escape or strip unsafe HTML), and then construct the modal markup
with the sanitized variables (or insert text nodes via DOM APIs instead of
innerHTML) so no raw user input is injected into innerHTML.

Comment on lines +430 to +446
const fieldsList = this.formFields.map(field => {
const requiredText = (field.required === 'yes' || field.required === true) ? ' (Required)' : '';
const fieldType = this.getFieldDisplayType(field);
return `<li>${field.label}${requiredText} - ${this.getFieldTypeDescription(fieldType)}</li>`;
}).join('');

const successMessage = {
type: 'ai',
content: `Perfect! I've created a "${this.formTitle}" form for you with the following fields:
<ul>${fieldsList}</ul>
${this.generatedFormData.form_description || 'The form is ready and you can customize it further in the form builder!'}`,
showButtons: false, // Don't show buttons for initial form creation
status: 'Successfully created the form.'
};

this.chatMessages.push(successMessage);
}
Copy link

Choose a reason for hiding this comment

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

πŸ’‘ Verification agent

🧩 Analysis chain

Sanitize field labels used in HTML chat content.

Field labels from AI/server are interpolated into an HTML string. If FormSuccessStage renders via v-html, that’s XSS.

  • Sanitize labels before concatenation:
-                const fieldsList = this.formFields.map(field => {
-                    const requiredText = (field.required === 'yes' || field.required === true) ? ' (Required)' : '';
-                    const fieldType = this.getFieldDisplayType(field);
-                    return `<li>${field.label}${requiredText} - ${this.getFieldTypeDescription(fieldType)}</li>`;
-                }).join('');
+                const fieldsList = this.formFields.map(field => {
+                    const requiredText = (field.required === 'yes' || field.required === true) ? ' (Required)' : '';
+                    const fieldType = this.getFieldDisplayType(field);
+                    return `<li>${this.sanitizeHtml(field.label)}${requiredText} - ${this.getFieldTypeDescription(fieldType)}</li>`;
+                }).join('');

Run to confirm whether v-html is used for chat content:


🏁 Script executed:

#!/bin/bash
rg -n --type=vue -C2 'v-html=.*content|v-html=.*message' assets/js/components

Length of output: 27


🏁 Script executed:

#!/bin/bash
rg -n 'v-html' -C2 --glob '*.vue' assets/js/components

Length of output: 3598


🏁 Script executed:

#!/bin/bash
rg -n 'sanitizeHtml' -C2 --glob '*.{vue,js,ts}' assets/js/components

Length of output: 0


Sanitize field labels before rendering via v-html
FormSuccessStage.vue (line 75) uses v-html="message.content", and that content is built in AIFormBuilder.vue (lines 430–446) by interpolating field.label into an HTML stringβ€”this opens an XSS vector. Wrap field.label in a sanitizer (e.g. import and use DOMPurify.sanitize(field.label)) before concatenating into fieldsList.

πŸ€– Prompt for AI Agents
In assets/js/components/AIFormBuilder.vue around lines 430 to 446, field.label
is interpolated directly into an HTML string which is later rendered with
v-html, creating an XSS risk; import a sanitizer (e.g. DOMPurify) at the top of
the file and call DOMPurify.sanitize(field.label) (or sanitize the assembled
fieldsList or final content) before concatenating into the HTML string so only
safe HTML is produced, then push the sanitized content into
successMessage.content.

Comment on lines +1 to +3
# WPUF Post Form Fields Reference

This document provides a comprehensive reference for all available fields in WPUF (WordPress User Frontend) post forms, including both free and pro fields. This reference is used by the AI form creation system to understand available field types, properties, and configurations.
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Prevent AI from copying simplified examples into outputs.

This reference shows minimal field blobs that omit required properties; when appended to prompts, models may mimic them. Add a top-note that examples are schematic only and that generation must follow system-prompt’s full schema.

Apply:

 # WPUF Post Form Fields Reference
+
+Note: Examples in this document are schematic for human reference. AI outputs MUST follow the exact, fully-expanded field schema defined in includes/AI/system-prompt.md and MUST NOT copy these minimal examples verbatim.
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# WPUF Post Form Fields Reference
This document provides a comprehensive reference for all available fields in WPUF (WordPress User Frontend) post forms, including both free and pro fields. This reference is used by the AI form creation system to understand available field types, properties, and configurations.
# WPUF Post Form Fields Reference
Note: Examples in this document are schematic for human reference. AI outputs MUST follow the exact, fully-expanded field schema defined in includes/AI/system-prompt.md and MUST NOT copy these minimal examples verbatim.
This document provides a comprehensive reference for all available fields in WPUF (WordPress User Frontend) post forms, including both free and pro fields. This reference is used by the AI form creation system to understand available field types, properties, and configurations.
πŸ€– Prompt for AI Agents
In includes/AI/wpuf-post-form-fields-reference.md around lines 1 to 3, add a
clear top-note before the existing content stating that the example field blobs
are schematic only and omit required properties, and that any generated forms
must comply with the full schema specified by the system prompt; keep the
wording short, unambiguous, and prominent (e.g., bold or header), and ensure it
instructs downstream generators to never rely on the minimal examples but to
validate and populate all required properties according to the official schema.

Comment on lines +152 to +156
### File Upload Options
- **max_size**: Maximum file size in bytes
- **allowed_extensions**: Allowed file extensions
- **max_files**: Maximum number of files

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unify file-size units with the system prompt (KB, not bytes).

This doc says β€œbytes,” while system-prompt instructs β€œKB” (e.g., 1024 = 1MB). Mismatch will cause limits to be misapplied.

Apply:

- **max_size**: Maximum file size in bytes
+ **max_size**: Maximum file size in KB (e.g., 1024 = 1 MB)

And:

- **max_size**: Maximum file size in bytes (default: 2MB)
+ **max_size**: Maximum file size in KB (default: 2048 KB = 2 MB)

Also applies to: 190-198

πŸ€– Prompt for AI Agents
In includes/AI/wpuf-post-form-fields-reference.md around lines 152-156 (and also
190-198), the file upload option descriptions use "bytes" but the system prompt
standardizes on "KB"; update the text to indicate sizes are in KB (e.g.,
"max_size: Maximum file size in KB (1024 = 1MB)"), and adjust any examples or
labels to KB units, ensuring consistency across both places mentioned.

Comment on lines +234 to +250
## Pro vs Free Fields

### Free Fields
All basic functionality is available without upgrading:
- Post fields (title, content, tags, categories)
- Basic input fields (text, email, textarea, radio, checkbox, dropdown)
- Layout fields (columns, sections)
- Basic utility fields (HTML, hidden, image upload, reCAPTCHA)

### Pro Fields
Advanced functionality requires WPUF Pro:
- User profile fields
- Social media fields
- Advanced input types (date, time, file, phone, address)
- Special fields (ratings, embed, multi-step, etc.)
- Custom taxonomy support beyond basic categories/tags

Copy link

Choose a reason for hiding this comment

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

πŸ’‘ Verification agent

🧩 Analysis chain

Confirm canonical names for β€œmultiple_select” and β€œcustom_hidden_field”.

If WPUF uses multiselect as a select variant or hidden_field as the slug, rename entries to canonical keys.


🏁 Script executed:

#!/bin/bash
rg -nP -C2 '\bmultiple_select\b|\bcustom_hidden_field\b' --type=php --type=js

Length of output: 129579


🏁 Script executed:

#!/bin/bash
rg -nP 'multiselect' -C2 --type=php --type=js

Length of output: 267704


Normalize canonical keys: decide & apply consistent names for Multi‑Select and Hidden fields

  • multiple_select vs multiselect β€” codebase uses both: template/field-manager use "multiple_select" (includes/class-field-manager.php, includes/Fields/Form_Field_MultiDropdown.php), while renderers/logic expect "multiselect" (class/render-form.php, wpuf-functions.php, admin template hidden_field sets input_type 'multiselect', assets/js field components use 'multiselect'). Action: pick one canonical input_type/data_type (recommend "multiselect") and align Form_Field_MultiDropdown, field-manager, admin templates, render-form, wpuf-functions, and JS (or add a normalization layer when loading/saving fields).

  • custom_hidden_field β€” template is "custom_hidden_field" across admin/UI and JS, but some code writes input_type 'hidden' while includes/Fields/Form_Field_Hidden.php sets input_type 'custom_hidden_field'. Action: decide whether input_type should be "hidden" or "custom_hidden_field" (recommend: keep template "custom_hidden_field" and use input_type "hidden" with explicit normalization), then update the field class, admin template, field-manager and JS accordingly.

Files with concrete pointers found: includes/Fields/Form_Field_MultiDropdown.php, includes/Fields/Form_Field_Hidden.php, includes/class-field-manager.php, Admin/Forms/Admin_Template.php (admin/template.php variants), class/render-form.php, wpuf-functions.php, assets/js/wpuf-form-builder-components.js and assets/js-templates/form-components.php.

πŸ€– Prompt for AI Agents
In includes/AI/wpuf-post-form-fields-reference.md around lines 234 to 250: the
repo mixes canonical keys for multi-select and hidden fields causing mismatches
between field classes, manager, renderers, admin templates and JS. Choose and
apply the canonical names (recommend "multiselect" for the multi-select
input_type/data_type and use "hidden" as the input_type with template name
"custom_hidden_field" but normalize on load/save), then update
includes/Fields/Form_Field_MultiDropdown.php,
includes/Fields/Form_Field_Hidden.php, includes/class-field-manager.php,
Admin/Forms/Admin_Template.php (and other admin templates),
class/render-form.php, wpuf-functions.php,
assets/js/wpuf-form-builder-components.js and
assets/js-templates/form-components.php to either use the chosen canonical keys
or add a single normalization layer where fields are loaded/saved that maps
"multiple_select"β†’"multiselect" and "custom_hidden_field"↔"hidden" so all
components consistently read/write the same input_type.

Comment on lines +366 to +375
### Supported Field Types
Fields that can be used in conditional logic:
- `radio_field`
- `checkbox_field`
- `dropdown_field`
- `text_field`
- `textarea_field`
- `email_address`
- `numeric_text_field`

Copy link

Choose a reason for hiding this comment

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

πŸ’‘ Verification agent

🧩 Analysis chain

Align conditional-capable field list with real implementation.

Ensure all listed templates support conditions in code to avoid UI mismatches.


🏁 Script executed:

#!/bin/bash
rg -nP -C3 'wpuf_cond|condition_status' --type=php

Length of output: 49863


🏁 Script executed:

#!/bin/bash
# List template definitions and default wpuf_cond values in each field class
for file in includes/Fields/Form_Field_*.php; do
  echo "$file"
  grep -nP "('template'|wpuf_cond)" -C1 "$file"
  echo "----"
done

Length of output: 2943


Update supported conditional field list in documentation
In includes/AI/wpuf-post-form-fields-reference.md (lines 366-375), revise the β€œSupported Field Types” list to exactly match the templates that initialize wpuf_cond (i.e. those that support conditions) and exclude any that don’t. For example, include every template with 'wpuf_cond' => $this->conditionals in Post_Form_Template_Post.php (e.g. radio_field, checkbox_field, dropdown_field, text_field, textarea_field, email_address, numeric_text_field, plus any others defined there) and omit ones with wpuf_cond => null (e.g. html, hidden, section_break, recaptcha, cloudflare_turnstile).

Comment on lines +482 to +499
### Template Structure
Each field template follows this structure:
```json
{
"template": "field_type",
"label": "Field Label",
"name": "field_name",
"required": "yes",
"help": "Help text",
"placeholder": "Placeholder text",
"default": "Default value",
"css": "custom-class",
"width": "100%",
"show_in_post": "yes",
"hide_field_label": "no",
"is_meta": "yes"
}
```
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Width value conflicts with prompt (β€˜small|medium|large’ vs β€˜100%’).

Use the same enum across docs and generators to avoid parser ambiguity.

Apply:

-  "width": "100%",
+  "width": "large",

And document allowed values: small | medium | large.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Template Structure
Each field template follows this structure:
```json
{
"template": "field_type",
"label": "Field Label",
"name": "field_name",
"required": "yes",
"help": "Help text",
"placeholder": "Placeholder text",
"default": "Default value",
"css": "custom-class",
"width": "100%",
"show_in_post": "yes",
"hide_field_label": "no",
"is_meta": "yes"
}
```
{
"template": "field_type",
"label": "Field Label",
"name": "field_name",
"required": "yes",
"help": "Help text",
"placeholder": "Placeholder text",
"default": "Default value",
"css": "custom-class",
"width": "large",
"show_in_post": "yes",
"hide_field_label": "no",
"is_meta": "yes"
}
πŸ€– Prompt for AI Agents
In includes/AI/wpuf-post-form-fields-reference.md around lines 482 to 499, the
example template uses a percentage value for "width" ("100%") which conflicts
with the intended enum values; update the example and any surrounding text to
use the canonical enum values (small | medium | large), add a short sentence
documenting the allowed values "small | medium | large", and ensure any
referenced generators/parsers are adjusted to accept and validate those enum
strings instead of percentage formats to avoid ambiguity.

@arifulhoque7
Copy link
Contributor Author

Duplicate PR . new PR

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 needs: testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants