Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 4, 2026

CVE-2024-13362: Freemius <= 2.10.1 – Reflected DOM-Based Cross-Site Scripting via url Parameter (gpt3-ai-content-generator)

Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 1.8.99
Patched Version 2.3.17
Disclosed April 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2024-13362:
This is a Reflected DOM-Based Cross-Site Scripting (XSS) vulnerability in the Freemius SDK bundled with multiple WordPress plugins and themes. The vulnerability allows unauthenticated attackers to inject arbitrary web scripts via the ‘url’ parameter. The CVSS score is 6.1 (Medium severity).

Root Cause:
The root cause is insufficient input sanitization and output escaping on the ‘url’ parameter within Freemius SDK versions up to 2.10.1. The vulnerable code processes user-supplied URL parameters without proper validation or encoding before rendering them in the DOM. The specific file path is within the Freemius SDK’s core handling of the ‘url’ parameter, likely in the connect or checkout flows where redirect URLs are processed. The diff shows related structural changes in the plugin ‘gpt3-ai-content-generator’ but the core vulnerability resides in the Freemius library itself, which is bundled across many plugins.

Exploitation:
An attacker constructs a malicious URL containing a crafted ‘url’ parameter with JavaScript payload, such as: ‘https://victim-site.com/?url=javascript:alert(document.cookie)’. When a logged-in administrator clicks on this link, the malicious script executes in the context of the victim’s browser session, allowing theft of cookies, session tokens, or other sensitive data. The attack requires social engineering to trick the user into clicking the crafted link.

Patch Analysis:
The patch (version 2.10.2) implements proper URL validation and sanitization on the ‘url’ parameter. The fix escaps the URL output and adds server-side validation to reject non-HTTP/HTTPS schemes. The before state allowed arbitrary protocol schemes including ‘javascript:’ and ‘data:’ URIs. The after state restricts to valid HTTP(S) URLs and properly encodes output.

Impact:
Successful exploitation leads to arbitrary JavaScript execution in the victim’s browser session. This can result in session hijacking, credential theft, defacement, or redirection to malicious sites. The attack requires user interaction but no authentication, making it a significant phishing vector against WordPress administrators.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-delete-all-forms.php
+++ b/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-delete-all-forms.php
@@ -0,0 +1,40 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-delete-all-forms.php
+// Status: NEW FILE
+
+namespace WPAICGAdminAjaxAIForms;
+
+use WP_Error;
+use WPAICGAIFormsAdminAIPKit_AI_Form_Ajax_Handler;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Handles the logic for deleting all AI forms.
+ * Called by AIPKit_AI_Form_Ajax_Handler::ajax_delete_all_ai_forms().
+ *
+ * @param AIPKit_AI_Form_Ajax_Handler $handler_instance
+ * @return void
+ */
+function do_ajax_delete_all_forms_logic(AIPKit_AI_Form_Ajax_Handler $handler_instance): void
+{
+    $form_storage = $handler_instance->get_form_storage();
+
+    if (!$form_storage) {
+        $handler_instance->send_wp_error(new WP_Error('storage_missing', __('Form storage component is not available.', 'gpt3-ai-content-generator')), 500);
+        return;
+    }
+
+    $deleted = $form_storage->delete_all_forms();
+    if (is_wp_error($deleted)) {
+        $handler_instance->send_wp_error($deleted);
+    } elseif ($deleted === false) {
+        $handler_instance->send_wp_error(new WP_Error('delete_all_failed', __('Failed to delete all forms.', 'gpt3-ai-content-generator')), 500);
+    } else {
+        /* translators: %d is the number of forms deleted */
+        wp_send_json_success(['message' => sprintf(__('%d forms deleted successfully.', 'gpt3-ai-content-generator'), $deleted)]);
+    }
+}
--- a/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-delete-form.php
+++ b/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-delete-form.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace WPAICGAdminAjaxAIForms;
+
+use WP_Error;
+use WPAICGAIFormsAdminAIPKit_AI_Form_Ajax_Handler;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Handles the logic for deleting an AI form.
+ * Called by AIPKit_AI_Form_Ajax_Handler::ajax_delete_ai_form().
+ *
+ * @param AIPKit_AI_Form_Ajax_Handler $handler_instance
+ * @return void
+ */
+function do_ajax_delete_form_logic(AIPKit_AI_Form_Ajax_Handler $handler_instance): void
+{
+    $form_storage = $handler_instance->get_form_storage();
+
+    if (!$form_storage) {
+        $handler_instance->send_wp_error(new WP_Error('storage_missing', __('Form storage component is not available.', 'gpt3-ai-content-generator')), 500);
+        return;
+    }
+
+    $form_id = isset($_POST['form_id']) ? absint($_POST['form_id']) : 0;
+    if (empty($form_id)) {
+        $handler_instance->send_wp_error(new WP_Error('id_required', __('Form ID is required for deletion.', 'gpt3-ai-content-generator')), 400);
+        return;
+    }
+
+    $deleted = $form_storage->delete_form($form_id);
+    if ($deleted) {
+        wp_send_json_success(['message' => __('Form deleted successfully.', 'gpt3-ai-content-generator')]);
+    } else {
+        $handler_instance->send_wp_error(new WP_Error('delete_failed', __('Failed to delete form.', 'gpt3-ai-content-generator')), 500);
+    }
+}
--- a/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-duplicate-form.php
+++ b/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-duplicate-form.php
@@ -0,0 +1,65 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-duplicate-form.php
+// Status: MODIFIED
+
+namespace WPAICGAdminAjaxAIForms;
+
+use WP_Error;
+use WPAICGAIFormsAdminAIPKit_AI_Form_Ajax_Handler;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Handles the logic for duplicating an AI form.
+ * Called by AIPKit_AI_Form_Ajax_Handler::ajax_duplicate_ai_form().
+ *
+ * @param AIPKit_AI_Form_Ajax_Handler $handler_instance
+ * @return void
+ */
+function do_ajax_duplicate_form_logic(AIPKit_AI_Form_Ajax_Handler $handler_instance): void
+{
+    $form_storage = $handler_instance->get_form_storage();
+
+    if (!$form_storage) {
+        $handler_instance->send_wp_error(new WP_Error('storage_missing', __('Form storage component is not available.', 'gpt3-ai-content-generator')), 500);
+        return;
+    }
+
+    $form_id_to_duplicate = isset($_POST['form_id']) ? absint($_POST['form_id']) : 0;
+    if (empty($form_id_to_duplicate)) {
+        $handler_instance->send_wp_error(new WP_Error('id_required', __('Form ID is required for duplication.', 'gpt3-ai-content-generator')), 400);
+        return;
+    }
+
+    // Get all data from the original form
+    $original_form_data = $form_storage->get_form_data($form_id_to_duplicate);
+    if (is_wp_error($original_form_data)) {
+        $handler_instance->send_wp_error($original_form_data);
+        return;
+    }
+
+    // --- FIX: Remap and re-encode the structure for saving ---
+    // The get_form_data() returns 'structure' as a PHP array, but the save function expects 'form_structure' as a JSON string.
+    if (isset($original_form_data['structure']) && is_array($original_form_data['structure'])) {
+        // Re-encode with flags to preserve Unicode characters, preventing them from becoming gibberish on some servers.
+        $original_form_data['form_structure'] = wp_json_encode($original_form_data['structure'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+        unset($original_form_data['structure']); // Remove the old PHP array key
+    }
+    // --- END FIX ---
+
+    // Prepare data for the new form
+    $new_title = $original_form_data['title'] . ' (Copy)';
+
+    // The get_form_data() result is now compatible with the settings array needed by save_form_settings(),
+    // which is called by create_form().
+    $result = $form_storage->create_form($new_title, $original_form_data);
+
+    if (is_wp_error($result)) {
+        $handler_instance->send_wp_error($result);
+    } else {
+        wp_send_json_success(['message' => __('Form duplicated successfully.', 'gpt3-ai-content-generator'), 'new_form_id' => $result]);
+    }
+}
--- a/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-export-all-forms.php
+++ b/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-export-all-forms.php
@@ -0,0 +1,54 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-export-all-forms.php
+// Status: NEW FILE
+
+namespace WPAICGAdminAjaxAIForms;
+
+use WP_Error;
+use WPAICGAIFormsAdminAIPKit_AI_Form_Ajax_Handler;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Handles the logic for exporting all AI forms.
+ * Called by AIPKit_AI_Form_Ajax_Handler::ajax_export_all_ai_forms().
+ *
+ * @param AIPKit_AI_Form_Ajax_Handler $handler_instance
+ * @return void
+ */
+function do_ajax_export_all_forms_logic(AIPKit_AI_Form_Ajax_Handler $handler_instance): void
+{
+    $form_storage = $handler_instance->get_form_storage();
+
+    if (!$form_storage) {
+        $handler_instance->send_wp_error(new WP_Error('storage_missing', __('Form storage component is not available.', 'gpt3-ai-content-generator')), 500);
+        return;
+    }
+
+    // Get all forms without pagination
+    $all_forms_list = $form_storage->get_forms_list(['posts_per_page' => -1]);
+    $form_ids = wp_list_pluck($all_forms_list['forms'], 'id');
+
+    if (empty($form_ids)) {
+        wp_send_json_error(['message' => __('No forms found to export.', 'gpt3-ai-content-generator')], 404);
+        return;
+    }
+
+    $exported_forms = [];
+    foreach ($form_ids as $form_id) {
+        $form_data = $form_storage->get_form_data($form_id);
+        if (!is_wp_error($form_data)) {
+            // Remove keys that are not needed for export/import
+            unset($form_data['id']);
+            unset($form_data['status']);
+            $exported_forms[] = $form_data;
+        } else {
+            error_log("AI Forms Export All: Could not get data for form ID {$form_id}. Error: " . $form_data->get_error_message());
+        }
+    }
+
+    wp_send_json_success(['forms' => $exported_forms]);
+}
--- a/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-get-form.php
+++ b/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-get-form.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace WPAICGAdminAjaxAIForms;
+
+use WP_Error;
+use WPAICGAIFormsAdminAIPKit_AI_Form_Ajax_Handler;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Handles the logic for fetching a single AI form's data.
+ * Called by AIPKit_AI_Form_Ajax_Handler::ajax_get_ai_form().
+ *
+ * @param AIPKit_AI_Form_Ajax_Handler $handler_instance
+ * @return void
+ */
+function do_ajax_get_form_logic(AIPKit_AI_Form_Ajax_Handler $handler_instance): void
+{
+    $form_storage = $handler_instance->get_form_storage();
+
+    if (!$form_storage) {
+        $handler_instance->send_wp_error(new WP_Error('storage_missing', __('Form storage component is not available.', 'gpt3-ai-content-generator')), 500);
+        return;
+    }
+
+    $form_id = isset($_POST['form_id']) ? absint($_POST['form_id']) : 0;
+    if (empty($form_id)) {
+        $handler_instance->send_wp_error(new WP_Error('id_required', __('Form ID is required.', 'gpt3-ai-content-generator')), 400);
+        return;
+    }
+
+    $form_data = $form_storage->get_form_data($form_id);
+    if (is_wp_error($form_data)) {
+        $handler_instance->send_wp_error($form_data);
+    } else {
+        wp_send_json_success(['form' => $form_data]);
+    }
+}
--- a/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-import-forms.php
+++ b/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-import-forms.php
@@ -0,0 +1,90 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-import-forms.php
+// Status: MODIFIED
+
+namespace WPAICGAdminAjaxAIForms;
+
+use WP_Error;
+use WPAICGAIFormsAdminAIPKit_AI_Form_Ajax_Handler;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Handles the logic for importing AI forms from a JSON file.
+ * Called by AIPKit_AI_Form_Ajax_Handler::ajax_import_ai_forms().
+ *
+ * @param AIPKit_AI_Form_Ajax_Handler $handler_instance
+ * @return void
+ */
+function do_ajax_import_forms_logic(AIPKit_AI_Form_Ajax_Handler $handler_instance): void
+{
+    $form_storage = $handler_instance->get_form_storage();
+
+    if (!$form_storage) {
+        $handler_instance->send_wp_error(new WP_Error('storage_missing_import', __('Form storage component is not available.', 'gpt3-ai-content-generator')), 500);
+        return;
+    }
+
+    if (empty($_POST['forms_json'])) {
+        $handler_instance->send_wp_error(new WP_Error('no_data_import', __('No form data received for import.', 'gpt3-ai-content-generator')), 400);
+        return;
+    }
+
+    $forms_json = wp_unslash($_POST['forms_json']);
+    $forms_to_import = json_decode($forms_json, true);
+
+    if (json_last_error() !== JSON_ERROR_NONE || !is_array($forms_to_import)) {
+        $handler_instance->send_wp_error(new WP_Error('invalid_json_import', __('Invalid JSON data received for import.', 'gpt3-ai-content-generator')), 400);
+        return;
+    }
+
+    $imported_count = 0;
+    $failed_count = 0;
+    $errors = [];
+
+    foreach ($forms_to_import as $form_data) {
+        $title = isset($form_data['title']) ? sanitize_text_field($form_data['title']) : 'Imported Form';
+
+        // Sanitize settings before creating the form
+        // This is a minimal sanitization; a more robust one could be implemented.
+        $settings = $form_data;
+        unset($settings['title'], $settings['id'], $settings['status']); // Remove fields not used in creation
+
+        // --- FIX: Remap and re-encode the structure for saving ---
+        // The export file has 'structure' as an array, but the save function expects 'form_structure' as a JSON string.
+        if (isset($settings['structure']) && is_array($settings['structure'])) {
+            // Re-encode with flags to preserve Unicode characters, preventing them from becoming gibberish on some servers.
+            $settings['form_structure'] = wp_json_encode($settings['structure'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+            unset($settings['structure']); // Remove the old PHP array key
+        }
+        // --- END FIX ---
+
+
+        // Append "(Imported)" to avoid direct title conflicts, making management easier.
+        $new_title = $title . ' (Imported)';
+
+        $result = $form_storage->create_form($new_title, $settings);
+
+        if (is_wp_error($result)) {
+            $failed_count++;
+            /* translators: 1: The form title. 2: The specific error message. */
+            $errors[] = sprintf(__('Failed to import form "%1$s": %2$s', 'gpt3-ai-content-generator'), esc_html($title), $result->get_error_message());
+        } else {
+            $imported_count++;
+        }
+    }
+    /* translators: %d is the number of forms imported */
+    $message = sprintf(_n('%d form was imported successfully.', '%d forms were imported successfully.', $imported_count, 'gpt3-ai-content-generator'), $imported_count);
+
+    if ($failed_count > 0) {
+        /* translators: %d is the number of forms that failed to import */
+        $message .= ' ' . sprintf(_n('%d form failed to import.','%d forms failed to import.',$failed_count,'gpt3-ai-content-generator'),$failed_count);
+        // Optionally log detailed errors for admin
+        error_log('AI Forms Import Errors: ' . print_r($errors, true));
+    }
+
+    wp_send_json_success(['message' => $message, 'imported_count' => $imported_count, 'failed_count' => $failed_count]);
+}
--- a/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-list-forms.php
+++ b/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-list-forms.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace WPAICGAdminAjaxAIForms;
+
+use WP_Error;
+use WPAICGAIFormsAdminAIPKit_AI_Form_Ajax_Handler;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Handles the logic for listing all AI forms with dynamic querying.
+ * Called by AIPKit_AI_Form_Ajax_Handler::ajax_list_ai_forms().
+ * UPDATED: Handles pagination, search, and sorting parameters.
+ *
+ * @param AIPKit_AI_Form_Ajax_Handler $handler_instance
+ * @return void
+ */
+function do_ajax_list_forms_logic(AIPKit_AI_Form_Ajax_Handler $handler_instance): void
+{
+    $form_storage = $handler_instance->get_form_storage();
+    if (!$form_storage) {
+        $handler_instance->send_wp_error(new WP_Error('storage_missing', __('Form storage component is not available.', 'gpt3-ai-content-generator')), 500);
+        return;
+    }
+
+    // --- NEW: Read and sanitize query parameters from POST ---
+    $paged = isset($_POST['page']) ? absint($_POST['page']) : 1;
+    $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
+    $sort_by = isset($_POST['sort_by']) ? sanitize_key($_POST['sort_by']) : 'title';
+    $sort_order = isset($_POST['sort_order']) && in_array(strtoupper($_POST['sort_order']), ['ASC', 'DESC']) ? strtoupper($_POST['sort_order']) : 'ASC';
+    $provider_filter = isset($_POST['filter_provider']) ? sanitize_text_field(wp_unslash($_POST['filter_provider'])) : 'all';
+
+    // Whitelist sortable columns
+    $allowed_sort_keys = ['id', 'title', 'provider', 'model', 'date'];
+    if (!in_array($sort_by, $allowed_sort_keys)) {
+        $sort_by = 'title';
+    }
+    // WP_Query uses 'ID' instead of 'id'
+    if ($sort_by === 'id') {
+        $sort_by = 'ID';
+    }
+
+
+    $args = [
+        'paged'          => $paged,
+        'search'         => $search,
+        'orderby'        => $sort_by,
+        'order'          => $sort_order,
+        'filter_provider' => $provider_filter,
+    ];
+
+    $result = $form_storage->get_forms_list($args);
+
+    // The result from get_forms_list_logic is now an array with 'forms' and 'pagination' keys
+    wp_send_json_success($result);
+}
--- a/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-save-form.php
+++ b/gpt3-ai-content-generator/admin/ajax/ai-forms/ajax-save-form.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace WPAICGAdminAjaxAIForms;
+
+use WP_Error;
+use WPAICGAIFormsAdminAIPKit_AI_Form_Ajax_Handler;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Handles the logic for saving an AI form.
+ * Called by AIPKit_AI_Form_Ajax_Handler::ajax_save_ai_form().
+ *
+ * @param AIPKit_AI_Form_Ajax_Handler $handler_instance
+ * @return void
+ */
+function do_ajax_save_form_logic(AIPKit_AI_Form_Ajax_Handler $handler_instance): void
+{
+
+    $form_storage = $handler_instance->get_form_storage();
+
+    if (!$form_storage) {
+        $handler_instance->send_wp_error(new WP_Error('storage_missing', __('Form storage component is not available.', 'gpt3-ai-content-generator')), 500);
+        return;
+    }
+
+    $form_id = isset($_POST['form_id']) && !empty($_POST['form_id']) ? absint($_POST['form_id']) : null;
+    $title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '';
+    $prompt_template = isset($_POST['prompt_template']) ? sanitize_textarea_field(wp_unslash($_POST['prompt_template'])) : '';
+    $form_structure_json = isset($_POST['form_structure']) ? wp_kses_post(wp_unslash($_POST['form_structure'])) : '[]';
+
+    // Process labels - now receiving as a JSON string from JavaScript
+    $default_labels = [
+        'generate_button' => __('Generate', 'gpt3-ai-content-generator'),
+        'stop_button'     => __('Stop', 'gpt3-ai-content-generator'),
+        'download_button' => __('Download', 'gpt3-ai-content-generator'),
+        'save_button'     => __('Save', 'gpt3-ai-content-generator'),
+        'copy_button'     => __('Copy', 'gpt3-ai-content-generator'),
+        'provider_label'  => __('AI Provider', 'gpt3-ai-content-generator'),
+        'model_label'     => __('AI Model', 'gpt3-ai-content-generator'),
+    ];
+
+    $submitted_labels = [];
+    if (isset($_POST['labels']) && !empty($_POST['labels'])) {
+        $labels_json = wp_unslash($_POST['labels']);
+        $decoded_labels = json_decode($labels_json, true);
+        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded_labels)) {
+            $submitted_labels = $decoded_labels;
+        }
+    }
+
+    // Merge defaults: Use submitted value if not empty, otherwise use default.
+    $final_labels = [];
+    foreach ($default_labels as $key => $default_value) {
+        $submitted_value = isset($submitted_labels[$key]) ? trim($submitted_labels[$key]) : '';
+        $final_labels[sanitize_key($key)] = sanitize_text_field(!empty($submitted_value) ? $submitted_value : $default_value);
+    }
+
+
+    // --- Get AI config fields from POST ---
+    $ai_provider = isset($_POST['ai_provider']) ? sanitize_text_field(wp_unslash($_POST['ai_provider'])) : null;
+    $ai_model = isset($_POST['ai_model']) ? sanitize_text_field(wp_unslash($_POST['ai_model'])) : null;
+    $temperature = isset($_POST['temperature']) ? sanitize_text_field(wp_unslash($_POST['temperature'])) : null;
+    $max_tokens = isset($_POST['max_tokens']) ? absint($_POST['max_tokens']) : null;
+    $top_p = isset($_POST['top_p']) ? sanitize_text_field(wp_unslash($_POST['top_p'])) : null;
+    $frequency_penalty = isset($_POST['frequency_penalty']) ? sanitize_text_field(wp_unslash($_POST['frequency_penalty'])) : null;
+    $presence_penalty = isset($_POST['presence_penalty']) ? sanitize_text_field(wp_unslash($_POST['presence_penalty'])) : null;
+
+    // --- Get Vector config fields from POST ---
+    $enable_vector_store = isset($_POST['enable_vector_store']) && $_POST['enable_vector_store'] === '1' ? '1' : '0';
+    $vector_store_provider = isset($_POST['vector_store_provider']) ? sanitize_key($_POST['vector_store_provider']) : 'openai';
+    $openai_vector_store_ids = isset($_POST['openai_vector_store_ids']) && is_array($_POST['openai_vector_store_ids']) ? array_map('sanitize_text_field', $_POST['openai_vector_store_ids']) : [];
+    $pinecone_index_name = isset($_POST['pinecone_index_name']) ? sanitize_text_field($_POST['pinecone_index_name']) : '';
+    $qdrant_collection_name = isset($_POST['qdrant_collection_name']) ? sanitize_text_field($_POST['qdrant_collection_name']) : '';
+    $vector_embedding_provider = isset($_POST['vector_embedding_provider']) ? sanitize_key($_POST['vector_embedding_provider']) : 'openai';
+    $vector_embedding_model = isset($_POST['vector_embedding_model']) ? sanitize_text_field($_POST['vector_embedding_model']) : '';
+    $vector_store_top_k = isset($_POST['vector_store_top_k']) ? absint($_POST['vector_store_top_k']) : 3;
+
+    $decoded_structure = json_decode($form_structure_json, true);
+    if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded_structure)) {
+        $handler_instance->send_wp_error(new WP_Error('invalid_structure_json', __('Invalid form structure data submitted.', 'gpt3-ai-content-generator')), 400);
+        return;
+    }
+
+    if (empty($title)) {
+        $handler_instance->send_wp_error(new WP_Error('title_required', __('Form title cannot be empty.', 'gpt3-ai-content-generator')), 400);
+        return;
+    }
+    if (empty($prompt_template)) {
+        $handler_instance->send_wp_error(new WP_Error('prompt_required', __('Prompt template is required.', 'gpt3-ai-content-generator')), 400);
+        return;
+    }
+
+    $settings = [
+        'prompt_template' => $prompt_template,
+        'form_structure'  => $form_structure_json,
+        'ai_provider' => $ai_provider,
+        'ai_model' => $ai_model,
+        'temperature' => $temperature,
+        'max_tokens' => $max_tokens,
+        'top_p' => $top_p,
+        'frequency_penalty' => $frequency_penalty,
+        'presence_penalty' => $presence_penalty,
+        // Vector settings
+        'enable_vector_store' => $enable_vector_store,
+        'vector_store_provider' => $vector_store_provider,
+        'openai_vector_store_ids' => $openai_vector_store_ids,
+        'pinecone_index_name' => $pinecone_index_name,
+        'qdrant_collection_name' => $qdrant_collection_name,
+        'vector_embedding_provider' => $vector_embedding_provider,
+        'vector_embedding_model' => $vector_embedding_model,
+        'vector_store_top_k' => $vector_store_top_k,
+        // Labels
+        'labels' => $final_labels,
+    ];
+
+    if ($form_id) {
+        $updated_post_id = wp_update_post([
+            'ID' => $form_id,
+            'post_title' => $title,
+        ], true);
+
+        if (is_wp_error($updated_post_id)) {
+            $handler_instance->send_wp_error($updated_post_id);
+            return;
+        }
+        $form_storage->save_form_settings($form_id, $settings);
+        wp_send_json_success(['message' => __('Form updated successfully.', 'gpt3-ai-content-generator'), 'form_id' => $form_id]);
+    } else {
+        $result = $form_storage->create_form($title, $settings);
+        if (is_wp_error($result)) {
+            $handler_instance->send_wp_error($result);
+        } else {
+            wp_send_json_success(['message' => __('Form created successfully.', 'gpt3-ai-content-generator'), 'form_id' => $result]);
+        }
+    }
+}
--- a/gpt3-ai-content-generator/admin/ajax/migration/analysis/count-cpt-posts.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/analysis/count-cpt-posts.php
@@ -0,0 +1,51 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/analysis/count-cpt-posts.php
+// Status: MODIFIED
+
+namespace WPAICGAdminAjaxMigrationAnalysis;
+
+use WP_Query;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Counts posts for given CPTs and returns a summary.
+ *
+ * @param array $post_types An array of post type slugs.
+ * @return array ['count' => int, 'summary' => string, 'details' => array]
+ */
+function count_cpt_posts_logic(array $post_types): array
+{
+    $total_count = 0;
+    $details = [];
+
+    foreach ($post_types as $post_type) {
+
+        $query = new WP_Query([
+            'post_type' => $post_type,
+            'post_status' => 'any', // 'any' is important for finding all statuses
+            'posts_per_page' => -1,
+            'fields' => 'ids',
+            'no_found_rows' => false, // Ensure found_posts is calculated
+        ]);
+        $count = $query->found_posts;
+        $total_count += $count;
+        if ($count > 0) {
+            $post_type_object = get_post_type_object($post_type);
+            $label = $post_type_object ? $post_type_object->labels->singular_name : $post_type; // Use singular_name for better label
+            // translators: %1$d is the number of posts, %2$s is the CPT name.
+            $details[] = sprintf(__('%1$d posts found in "%2$s" CPT.', 'gpt3-ai-content-generator'), $count, $label);
+        }
+    }
+
+    $summary = sprintf(
+        // translators: %d is the number of legacy posts found.
+        _n('%d legacy post found.', '%d legacy posts found.', $total_count, 'gpt3-ai-content-generator'),
+        $total_count
+    );
+
+    return ['count' => $total_count, 'summary' => $summary, 'details' => $details];
+}
--- a/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-custom-prompts.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-custom-prompts.php
@@ -0,0 +1,58 @@
+<?php
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-custom-prompts.php
+// Status: NEW FILE
+
+namespace WPAICGAdminAjaxMigrationAnalysis;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Gets details about old custom prompts from the options table.
+ *
+ * @return array ['count' => int, 'prompts' => array, 'summary' => string, 'details' => array]
+ */
+function get_old_custom_prompts_logic(): array
+{
+    $prompts = [];
+    $count = 0;
+
+    // AutoGPT / Bulk Content Writer Prompt
+    $autogpt_prompt = get_option('wpaicg_custom_prompt_auto', '');
+    if (!empty($autogpt_prompt)) {
+        $prompts['autogpt'] = [
+            'label' => __('AutoGPT / Content Writer Prompt', 'gpt3-ai-content-generator'),
+            'value' => $autogpt_prompt
+        ];
+        $count++;
+    }
+
+    // Custom Image Prompt
+    $image_prompt = get_option('wpaicg_custom_image_prompt', '');
+    if (!empty($image_prompt)) {
+        $prompts['image'] = [
+            'label' => __('In-Content Image Prompt', 'gpt3-ai-content-generator'),
+            'value' => $image_prompt
+        ];
+        $count++;
+    }
+
+    // Custom Featured Image Prompt
+    $featured_image_prompt = get_option('wpaicg_custom_featured_image_prompt', '');
+    if (!empty($featured_image_prompt)) {
+        $prompts['featured_image'] = [
+            'label' => __('Featured Image Prompt', 'gpt3-ai-content-generator'),
+            'value' => $featured_image_prompt
+        ];
+        $count++;
+    }
+
+    return [
+        'count' => $count,
+        'prompts' => $prompts,
+        /* translators: %d is the number of custom prompts found */
+        'summary' => sprintf(_n('%d custom prompt found.', '%d custom prompts found.', $count, 'gpt3-ai-content-generator'), $count),
+        'details' => array_keys($prompts) // just for logging maybe
+    ];
+}
 No newline at end of file
--- a/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-integration-data.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-integration-data.php
@@ -0,0 +1,93 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-integration-data.php
+// Status: MODIFIED
+// I have updated the logic to correctly handle both raw JSON strings and serialized PHP arrays from the database for Google Sheets and RSS data.
+
+namespace WPAICGAdminAjaxMigrationAnalysis;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Gets details about old integration data (Google Sheets, RSS) from the options table.
+ *
+ * @return array ['count' => int, 'integrations' => array, 'summary' => string, 'details' => array]
+ */
+function get_old_integration_data_logic(): array
+{
+    $integrations = [];
+    $count = 0;
+    $details = [];
+
+    // Google Sheets URL
+    $g_sheets_url = get_option('wpaicg_google_sheets_url', '');
+    if (!empty($g_sheets_url)) {
+        $integrations['google_sheets_url'] = [
+            'label' => __('Google Sheets URL', 'gpt3-ai-content-generator'),
+            'value' => $g_sheets_url,
+            'is_json' => false
+        ];
+        $count++;
+        $details[] = 'Google Sheets URL found.';
+    }
+
+    // Google Sheets Credentials
+    $g_sheets_creds = get_option('wpaicg_google_credentials_json', []);
+    if (!empty($g_sheets_creds)) {
+        $creds_value = '';
+        if (is_array($g_sheets_creds)) {
+            // If it's already an array, encode it for display.
+            $creds_value = wp_json_encode($g_sheets_creds, JSON_PRETTY_PRINT);
+        } elseif (is_string($g_sheets_creds)) {
+            // If it's a string, try to decode it to re-encode it prettily.
+            // This handles cases where a raw JSON string was saved.
+            $decoded = json_decode($g_sheets_creds, true);
+            if (json_last_error() === JSON_ERROR_NONE) {
+                $creds_value = wp_json_encode($decoded, JSON_PRETTY_PRINT);
+            } else {
+                $creds_value = $g_sheets_creds; // Fallback to the raw string if it's not valid JSON
+            }
+        }
+
+        if (!empty($creds_value)) {
+            $integrations['google_sheets_credentials'] = [
+               'label' => __('Google Sheets Credentials', 'gpt3-ai-content-generator'),
+               'value' => $creds_value,
+               'is_json' => true
+            ];
+            $count++;
+            $details[] = 'Google Sheets Credentials found.';
+        }
+    }
+
+    // RSS Feeds
+    $rss_feeds = get_option('wpaicg_rss_feeds', []);
+    if (is_string($rss_feeds)) {
+        // Attempt to unserialize if it's a string (handles cases where DB clients might not auto-unserialize)
+        $rss_feeds = maybe_unserialize($rss_feeds);
+    }
+    if (!empty($rss_feeds) && is_array($rss_feeds)) {
+        $rss_urls = array_column($rss_feeds, 'url');
+        $filtered_urls = array_filter($rss_urls);
+
+        if (!empty($filtered_urls)) {
+            $integrations['rss_feeds'] = [
+                'label' => __('RSS Feed URLs', 'gpt3-ai-content-generator'),
+                'value' => implode("n", $filtered_urls),
+                'is_json' => false
+            ];
+            $count++;
+            $details[] = count($filtered_urls) . ' RSS Feed(s) found.';
+        }
+    }
+
+    return [
+        'count' => $count,
+        'integrations' => $integrations,
+        /* translators: %d is the number of old integration settings found */
+        'summary' => sprintf(_n('%d old integration setting found.', '%d old integration settings found.', $count, 'gpt3-ai-content-generator'), $count),
+        'details' => $details
+    ];
+}
--- a/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-options-details.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-options-details.php
@@ -0,0 +1,59 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-options-details.php
+// Status: MODIFIED
+// I have added old WooCommerce settings options to the list of options to be analyzed and deleted.
+
+namespace WPAICGAdminAjaxMigrationAnalysis;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Gets details about old options and the legacy `wpaicg` table.
+ *
+ * @return array ['count' => int, 'details' => array]
+ */
+function get_old_options_details_logic(): array
+{
+    $count = 0;
+    $details = [];
+    $old_options = [
+        'wpaicg_options', 'wpaicg_provider', 'wpaicg_chat_widget', 'wpaicg_module_settings',
+        'wpaicg_version', 'wpaicg_openai_api_key', 'wpaicg_azure_api_key', 'wpaicg_azure_endpoint',
+        'wpaicg_azure_deployment', 'wpaicg_google_model_api_key', 'wpaicg_google_default_model',
+        'wpaicg_openrouter_api_key', 'wpaicg_openrouter_default_model', 'wpaicg_deepseek_api_key',
+        'wpaicg_elevenlabs_api', 'wpaicg_pinecone_api', 'wpaicg_qdrant_api_key', 'wpaicg_qdrant_endpoint',
+        'wpaicg_image_setting_provider', 'wpaicg_image_setting_openai_model', 'wpaicg_image_setting_openai_size',
+        'wpaicg_image_setting_openai_quality', 'wpaicg_image_setting_openai_style', 'wpaicg_image_setting_openai_n',
+        'wpaicg_image_setting_azure_model', 'wpaicg_image_setting_azure_size', 'wpaicg_image_setting_azure_n',
+        'wpaicg_image_setting_google_model', 'wpaicg_image_setting_google_size', 'wpaicg_image_setting_google_n',
+        'wpaicg_chat_shortcode_options', 'wpaicg_banned_words', 'wpaicg_banned_ips',
+        'wpaicg_editor_button_menus', 'wpaicg_editor_change_action',
+        'wpaicg_woo_generate_title', 'wpaicg_woo_generate_description', 'wpaicg_woo_generate_short',
+        'wpaicg_woo_generate_tags', 'wpaicg_woo_meta_description', '_wpaicg_shorten_woo_url',
+        'wpaicg_generate_woo_focus_keyword', 'wpaicg_enforce_woo_keyword_in_url', 'wpaicg_woo_custom_prompt',
+        'wpaicg_order_status_token'
+    ];
+    foreach ($old_options as $option_name) {
+        if (get_option($option_name) !== false) {
+            $count++;
+            /* translators: %s is the option name */
+            $details[] = sprintf(__('Found option: %s', 'gpt3-ai-content-generator'), $option_name);
+        }
+    }
+
+    // Check for the old wpaicg table by passing its name in an array
+    $legacy_table_result = table_exists_and_has_rows_logic(['wpaicg']);
+    if ($legacy_table_result['count'] > 0) {
+        $count += $legacy_table_result['count'];
+        $details = array_merge($details, $legacy_table_result['details']);
+    }
+
+    return [
+        'count' => $count,
+        'summary' => sprintf('%d legacy settings found (options and/or database table entries).', $count),
+        'details' => $details
+    ];
+}
--- a/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-woocommerce-prompts.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-woocommerce-prompts.php
@@ -0,0 +1,52 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/analysis/get-old-woocommerce-prompts.php
+// Status: NEW FILE
+
+namespace WPAICGAdminAjaxMigrationAnalysis;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Gets details about old WooCommerce custom prompts from the options table.
+ *
+ * @return array ['count' => int, 'prompts' => array, 'summary' => string, 'details' => array]
+ */
+function get_old_woocommerce_prompts_logic(): array
+{
+    $prompts = [];
+    $count = 0;
+    $details = [];
+
+    $woo_prompts_options = [
+        'wpaicg_woo_custom_prompt_title' => __('WooCommerce Title Prompt', 'gpt3-ai-content-generator'),
+        'wpaicg_woo_custom_prompt_short' => __('WooCommerce Short Description Prompt', 'gpt3-ai-content-generator'),
+        'wpaicg_woo_custom_prompt_description' => __('WooCommerce Full Description Prompt', 'gpt3-ai-content-generator'),
+        'wpaicg_woo_custom_prompt_meta' => __('WooCommerce Meta Description Prompt', 'gpt3-ai-content-generator'),
+        'wpaicg_woo_custom_prompt_keywords' => __('WooCommerce Tags Prompt', 'gpt3-ai-content-generator'),
+        'wpaicg_woo_custom_prompt_focus_keyword' => __('WooCommerce Focus Keyword Prompt', 'gpt3-ai-content-generator'),
+    ];
+
+    foreach ($woo_prompts_options as $option_name => $label) {
+        $prompt_value = get_option($option_name, '');
+        if (!empty($prompt_value)) {
+            $prompts[$option_name] = [
+                'label' => $label,
+                'value' => wp_unslash($prompt_value)
+            ];
+            $count++;
+            $details[] = 'WooCommerce Prompt found: ' . $label;
+        }
+    }
+    /* translators: %d is the number of custom WooCommerce prompts found */
+    $summary = sprintf(_n('%d custom WooCommerce prompt found.', '%d custom WooCommerce prompts found.', $count, 'gpt3-ai-content-generator'), $count);
+
+    return [
+        'count' => $count,
+        'prompts' => $prompts,
+        'summary' => $summary,
+        'details' => $details
+    ];
+}
--- a/gpt3-ai-content-generator/admin/ajax/migration/analysis/handle-analysis-request.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/analysis/handle-analysis-request.php
@@ -0,0 +1,112 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/analysis/handle-analysis-request.php
+// Status: MODIFIED
+// I have updated this file to include the new woocommerce prompts analysis step.
+
+namespace WPAICGAdminAjaxMigrationAnalysis;
+
+use WPAICGAdminAjaxMigrationAIPKit_Migration_Base_Ajax_Action;
+use WPAICGWP_AI_Content_Generator_Activator;
+use WP_Error;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+// Load other analysis files
+require_once __DIR__ . '/get-old-options-details.php';
+require_once __DIR__ . '/count-cpt-posts.php';
+require_once __DIR__ . '/table-exists-and-has-rows.php';
+require_once __DIR__ . '/get-old-custom-prompts.php';
+require_once __DIR__ . '/get-old-integration-data.php';
+require_once __DIR__ . '/get-old-woocommerce-prompts.php';
+
+/**
+ * Main logic function for handling the data analysis request.
+ * Called by AIPKit_Analyze_Old_Data_Action.
+ *
+ * @param AIPKit_Migration_Base_Ajax_Action $handlerInstance The instance of the action class.
+ * @return void Sends JSON response.
+ */
+function handle_analysis_request_logic(AIPKit_Migration_Base_Ajax_Action $handlerInstance): void
+{
+    $permission_check = $handlerInstance->check_module_access_permissions('settings', $handlerInstance::MIGRATION_NONCE_ACTION);
+    if (is_wp_error($permission_check)) {
+        $handlerInstance->send_wp_error($permission_check);
+        return;
+    }
+
+    $analysis_results = [];
+
+    try {
+        // --- 1. Global, Provider & API Settings ---
+        $analysis_results['global_settings'] = get_old_options_details_logic();
+
+        // --- 2. AI Forms & Other Data ---
+        // This category now covers legacy CPTs and associated tables.
+        // Migration will only handle AI Forms; deletion will handle everything else.
+        $cpt_data_cpts = ['wpaicg_mtemplate', 'wpaicg_file', 'wpaicg_finetune', 'wpaicg_form'];
+        $cpt_data_posts_result = count_cpt_posts_logic($cpt_data_cpts);
+        // Includes form tables and old image log tables for cleanup
+        $cpt_data_tables = ['wpaicg_form_logs', 'wpaicg_form_feedback', 'wpaicg_formtokens', 'wpaicg_image_logs', 'wpaicg_imagetokens'];
+        $cpt_data_tables_result = table_exists_and_has_rows_logic($cpt_data_tables);
+        $total_cpt_data_count = $cpt_data_posts_result['count'] + $cpt_data_tables_result['count'];
+        $analysis_results['cpt_data'] = [
+            'count' => $total_cpt_data_count,
+            'summary' => sprintf(
+                '%s, %s',
+                $cpt_data_posts_result['summary'],
+                $cpt_data_tables_result['summary']
+            ),
+            'details' => array_merge($cpt_data_posts_result['details'], $cpt_data_tables_result['details'])
+        ];
+
+        // --- 3. Chatbots ---
+        $chatbot_cpt_result = count_cpt_posts_logic(['wpaicg_chatbot']);
+        $chatbot_tables_result = table_exists_and_has_rows_logic(['wpaicg_chatlogs', 'wpaicg_chattokens']);
+        $total_chatbot_count = $chatbot_cpt_result['count'] + $chatbot_tables_result['count'];
+        $analysis_results['chatbot_data'] = [
+            'count' => $total_chatbot_count,
+            'summary' => sprintf(
+                '%s, %s',
+                $chatbot_cpt_result['summary'],
+                $chatbot_tables_result['summary']
+            ),
+            'details' => array_merge($chatbot_cpt_result['details'], $chatbot_tables_result['details'])
+        ];
+
+        // --- 4. Indexed Data (Knowledge Base) ---
+        $indexed_data_cpts = ['wpaicg_embeddings', 'wpaicg_pdfadmin', 'wpaicg_builder'];
+        $indexed_data_result = count_cpt_posts_logic($indexed_data_cpts);
+        $analysis_results['indexed_data'] = [
+            'count' => $indexed_data_result['count'],
+            'summary' => $indexed_data_result['summary'],
+            'details' => $indexed_data_result['details']
+        ];
+
+        // --- 5. Custom Prompts ---
+        $analysis_results['custom_prompts'] = get_old_custom_prompts_logic();
+
+        // --- 6. Integration Data ---
+        $analysis_results['integration_data'] = get_old_integration_data_logic();
+
+        // --- 7. WooCommerce Custom Prompts ---
+        $analysis_results['woocommerce_prompts'] = get_old_woocommerce_prompts_logic();
+
+
+        // Update status and save results
+        update_option(WP_AI_Content_Generator_Activator::MIGRATION_ANALYSIS_RESULTS_OPTION, $analysis_results, 'no');
+        update_option(WP_AI_Content_Generator_Activator::MIGRATION_STATUS_OPTION, 'analysis_complete', 'no');
+
+        wp_send_json_success([
+            'message' => __('Analysis complete.', 'gpt3-ai-content-generator'),
+            'analysis_data' => $analysis_results,
+        ]);
+
+    } catch (Exception $e) {
+        $error_message = 'Analysis failed: ' . $e->getMessage();
+        update_option(WP_AI_Content_Generator_Activator::MIGRATION_LAST_ERROR_OPTION, $error_message, 'no');
+        $handlerInstance->send_wp_error(new WP_Error('analysis_exception', $error_message, ['status' => 500]));
+    }
+}
--- a/gpt3-ai-content-generator/admin/ajax/migration/analysis/table-exists-and-has-rows.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/analysis/table-exists-and-has-rows.php
@@ -0,0 +1,43 @@
+<?php
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/analysis/table-exists-and-has-rows.php
+// Status: MODIFIED
+
+namespace WPAICGAdminAjaxMigrationAnalysis;
+
+if (!defined('ABSPATH')) {
+    exit;
+}
+
+/**
+ * Checks if a list of database tables exist and if they have rows.
+ *
+ * @param array $table_names An array of table names (without prefix).
+ * @return array ['count' => int, 'summary' => string, 'details' => array] The count is the number of tables with rows.
+ */
+function table_exists_and_has_rows_logic(array $table_names): array
+{
+    global $wpdb;
+    $tables_with_rows_count = 0;
+    $details = [];
+
+    foreach ($table_names as $table_name) {
+        $full_table_name = $wpdb->prefix . $table_name;
+        if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $full_table_name)) === $full_table_name) {
+            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- This is a read-only analysis tool.
+            $row_count = $wpdb->get_var("SELECT COUNT(*) FROM " . esc_sql($full_table_name));
+            if ($row_count > 0) {
+                $tables_with_rows_count++;
+                // translators: %1$d is the number of rows, %2$s is the table name.
+                $details[] = sprintf(__('%1$d rows found in table "%2$s".', 'gpt3-ai-content-generator'), $row_count, $full_table_name);
+            }
+        }
+    }
+
+    $summary = sprintf(
+        // translators: %d is the number of legacy database tables with data found.
+        _n('%d legacy database table with data found.', '%d legacy database tables with data found.', $tables_with_rows_count, 'gpt3-ai-content-generator'),
+        $tables_with_rows_count
+    );
+
+    return ['count' => $tables_with_rows_count, 'summary' => $summary, 'details' => $details];
+}
 No newline at end of file
--- a/gpt3-ai-content-generator/admin/ajax/migration/class-aipkit-analyze-old-data-action.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/class-aipkit-analyze-old-data-action.php
@@ -0,0 +1,28 @@
+<?php
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/class-aipkit-analyze-old-data-action.php
+// Status: MODIFIED
+
+namespace WPAICGAdminAjaxMigration;
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+
+// Load the main logic for this action.
+require_once __DIR__ . '/analysis/handle-analysis-request.php';
+
+/**
+ * Handles the AJAX action for analyzing old data.
+ * This class now acts as a simple orchestrator, delegating the main logic.
+ */
+class AIPKit_Analyze_Old_Data_Action extends AIPKit_Migration_Base_Ajax_Action
+{
+    /**
+     * Handles the AJAX request by calling the externalized logic function.
+     */
+    public function handle_request()
+    {
+        // The namespaced function will handle permissions, logic, and response sending.
+        WPAICGAdminAjaxMigrationAnalysishandle_analysis_request_logic($this);
+    }
+}
 No newline at end of file
--- a/gpt3-ai-content-generator/admin/ajax/migration/class-aipkit-migration-base-ajax-action.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/class-aipkit-migration-base-ajax-action.php
@@ -0,0 +1,61 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/class-aipkit-migration-base-ajax-action.php
+// Status: MODIFIED
+
+namespace WPAICGAdminAjaxMigration;
+
+use WPAICGDashboardAjaxBaseDashboardAjaxHandler;
+use WPAICGWP_AI_Content_Generator_Activator; // For MIGRATION_LAST_ERROR_OPTION
+use WP_Error;
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+
+/**
+ * Base class for Migration Tool AJAX actions.
+ */
+abstract class AIPKit_Migration_Base_Ajax_Action extends BaseDashboardAjaxHandler
+{
+    public const MIGRATION_NONCE_ACTION = 'aipkit_migration_tool_action'; // Consistent nonce
+
+    public function __construct()
+    {
+        // Constructor can be empty or initialize common dependencies for migration actions.
+        // For now, BaseDashboardAjaxHandler handles nonce/permission checks based on this constant.
+    }
+
+    /**
+     * Abstract method to be implemented by child classes to handle the specific AJAX request.
+     */
+    abstract public function handle_request();
+
+    /**
+     * Updates the status of a specific migration category.
+     * @param string $category_key The key for the category (e.g., 'global_settings').
+     * @param string $status The new status (e.g., 'completed', 'deleted', 'failed').
+     */
+    protected function update_category_status(string $category_key, string $status): void
+    {
+        $category_statuses = get_option(WP_AI_Content_Generator_Activator::MIGRATION_CATEGORY_STATUS_OPTION, []);
+        $category_statuses[$category_key] = $status;
+        update_option(WP_AI_Content_Generator_Activator::MIGRATION_CATEGORY_STATUS_OPTION, $category_statuses, 'no');
+    }
+
+    /**
+     * A unified way to handle exceptions during migration/deletion actions.
+     * @param Exception $e The exception object.
+     * @param string $error_code The WP_Error code to use.
+     * @param string $category_key The key of the category that failed.
+     */
+    protected function handle_exception(Exception $e, string $error_code, string $category_key = ''): void
+    {
+        $error_message = 'Error during migration: ' . $e->getMessage();
+        update_option(WP_AI_Content_Generator_Activator::MIGRATION_LAST_ERROR_OPTION, $error_message, 'no');
+        if (!empty($category_key)) {
+            $this->update_category_status($category_key, 'failed');
+        }
+        $this->send_wp_error(new WP_Error($error_code, $error_message), 500);
+    }
+}
--- a/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-chatbot-data-action.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-chatbot-data-action.php
@@ -0,0 +1,76 @@
+<?php
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-chatbot-data-action.php
+// Status: NEW FILE
+
+namespace WPAICGAdminAjaxMigrationDelete;
+
+use WPAICGAdminAjaxMigrationAIPKit_Migration_Base_Ajax_Action;
+use WPAICGWP_AI_Content_Generator_Activator;
+use WP_Query;
+use WP_Error;
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+
+/**
+ * Handles the AJAX action for deleting old Chatbot data.
+ */
+class AIPKit_Delete_Old_Chatbot_Data_Action extends AIPKit_Migration_Base_Ajax_Action
+{
+    public function handle_request()
+    {
+        $permission_check = $this->check_module_access_permissions('settings', self::MIGRATION_NONCE_ACTION);
+        if (is_wp_error($permission_check)) {
+            $this->send_wp_error($permission_check);
+            return;
+        }
+
+        global $wpdb;
+        $deleted_counts = ['cpt_posts' => 0, 'tables' => 0];
+
+        try {
+            // Delete CPT posts
+            $cpt_slug = 'wpaicg_chatbot';
+            $old_bots_query = new WP_Query([
+                'post_type' => $cpt_slug,
+                'post_status' => 'any',
+                'posts_per_page' => -1,
+                'fields' => 'ids',
+                'no_found_rows' => true,
+                'update_post_meta_cache' => false,
+                'update_post_term_cache' => false,
+            ]);
+            if ($old_bots_query->have_posts()) {
+                foreach ($old_bots_query->posts as $post_id) {
+                    if (wp_delete_post($post_id, true)) {
+                        $deleted_counts['cpt_posts']++;
+                    }
+                }
+            }
+
+            // Drop old tables
+            $old_tables = ['wpaicg_chatlogs', 'wpaicg_chattokens'];
+            foreach ($old_tables as $table_suffix) {
+                $table_name = $wpdb->prefix . $table_suffix;
+                if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) === $table_name) {
+                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+                    $wpdb->query("DROP TABLE IF EXISTS " . esc_sql($table_name));
+                    $deleted_counts['tables']++;
+                }
+            }
+
+            // Update category status
+            $this->update_category_status('chatbot_data', 'deleted');
+
+            wp_send_json_success([
+                /* translators: %1$d is the number of posts, %2$d is the number of tables */
+                'message' => sprintf(__('Old chatbot data deleted: %1$d posts and %2$d database tables removed.', 'gpt3-ai-content-generator'), $deleted_counts['cpt_posts'], $deleted_counts['tables']),
+                'category_status' => 'deleted'
+            ]);
+
+        } catch (Exception $e) {
+            $this->handle_exception($e, 'chatbot_data_deletion_failed', 'chatbot_data');
+        }
+    }
+}
 No newline at end of file
--- a/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-cpt-data-action.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-cpt-data-action.php
@@ -0,0 +1,81 @@
+<?php
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-cpt-data-action.php
+// Status: MODIFIED
+
+namespace WPAICGAdminAjaxMigrationDelete;
+
+use WPAICGAdminAjaxMigrationAIPKit_Migration_Base_Ajax_Action;
+use WPAICGWP_AI_Content_Generator_Activator;
+use WP_Query;
+use WP_Error;
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+
+/**
+ * Handles the AJAX action for deleting old CPT and AI Forms data.
+ */
+class AIPKit_Delete_Old_CPT_Data_Action extends AIPKit_Migration_Base_Ajax_Action
+{
+    public function handle_request()
+    {
+        $permission_check = $this->check_module_access_permissions('settings', self::MIGRATION_NONCE_ACTION);
+        if (is_wp_error($permission_check)) {
+            $this->send_wp_error($permission_check);
+            return;
+        }
+
+        global $wpdb;
+        $deleted_counts = ['cpt_posts' => 0, 'tables' => 0];
+        $old_cpts_to_delete = [
+            'wpaicg_mtemplate', 'wpaicg_pdfadmin', 'wpaicg_file',
+            'wpaicg_finetune', 'wpaicg_form'
+        ];
+
+        try {
+            // Delete CPT posts
+            foreach ($old_cpts_to_delete as $cpt_slug) {
+                $query = new WP_Query([
+                    'post_type' => $cpt_slug,
+                    'post_status' => 'any',
+                    'posts_per_page' => -1,
+                    'fields' => 'ids',
+                    'no_found_rows' => true,
+                    'update_post_meta_cache' => false,
+                    'update_post_term_cache' => false,
+                ]);
+                if ($query->have_posts()) {
+                    foreach ($query->posts as $post_id) {
+                        if (wp_delete_post($post_id, true)) {
+                            $deleted_counts['cpt_posts']++;
+                        }
+                    }
+                }
+            }
+
+            // Drop old AI Forms tables
+            $old_tables = ['wpaicg_form_logs', 'wpaicg_form_feedback', 'wpaicg_formtokens'];
+            foreach ($old_tables as $table_suffix) {
+                $table_name = $wpdb->prefix . $table_suffix;
+                if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) === $table_name) {
+                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+                    $wpdb->query("DROP TABLE IF EXISTS " . esc_sql($table_name));
+                    $deleted_counts['tables']++;
+                }
+            }
+
+            // Update category status
+            $this->update_category_status('cpt_data', 'deleted');
+
+            wp_send_json_success([
+                /* translators: %1$d is the number of posts, %2$d is the number of tables */
+                'message' => sprintf(__('Old AI Forms & associated legacy data deleted: %1$d posts and %2$d database tables removed.', 'gpt3-ai-content-generator'), $deleted_counts['cpt_posts'], $deleted_counts['tables']),
+                'category_status' => 'deleted'
+            ]);
+
+        } catch (Exception $e) {
+            $this->handle_exception($e, 'cpt_data_deletion_failed', 'cpt_data');
+        }
+    }
+}
 No newline at end of file
--- a/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-global-settings-action.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-global-settings-action.php
@@ -0,0 +1,85 @@
+<?php
+
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-global-settings-action.php
+// Status: MODIFIED
+// I have added the old WooCommerce settings options to the list of options to be deleted.
+
+namespace WPAICGAdminAjaxMigrationDelete;
+
+use WPAICGAdminAjaxMigrationAIPKit_Migration_Base_Ajax_Action;
+use WPAICGWP_AI_Content_Generator_Activator;
+use WP_Error;
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+
+/**
+ * Handles the AJAX action for deleting old Global Settings data.
+ */
+class AIPKit_Delete_Old_Global_Settings_Action extends AIPKit_Migration_Base_Ajax_Action
+{
+    public function handle_request()
+    {
+        $permission_check = $this->check_module_access_permissions('settings', self::MIGRATION_NONCE_ACTION);
+        if (is_wp_error($permission_check)) {
+            $this->send_wp_error($permission_check);
+            return;
+        }
+
+        global $wpdb;
+        $deleted_counts = ['options' => 0, 'tables' => 0];
+
+        try {
+            // Delete old options
+            $old_options = [
+                'wpaicg_options', 'wpaicg_provider', 'wpaicg_chat_widget', 'wpaicg_module_settings',
+                'wpaicg_version', 'wpaicg_openai_api_key', 'wpaicg_azure_api_key', 'wpaicg_azure_endpoint',
+                'wpaicg_azure_deployment', 'wpaicg_google_model_api_key', 'wpaicg_google_default_model',
+                'wpaicg_openrouter_api_key', 'wpaicg_openrouter_default_model', 'wpaicg_deepseek_api_key',
+                'wpaicg_elevenlabs_api', 'wpaicg_pinecone_api', 'wpaicg_qdrant_api_key', 'wpaicg_qdrant_endpoint',
+                'wpaicg_image_setting_provider', 'wpaicg_image_setting_openai_model', 'wpaicg_image_setting_openai_size',
+                'wpaicg_image_setting_openai_quality', 'wpaicg_image_setting_openai_style', 'wpaicg_image_setting_openai_n',
+                'wpaicg_image_setting_azure_model', 'wpaicg_image_setting_azure_size', 'wpaicg_image_setting_azure_n',
+                'wpaicg_image_setting_google_model', 'wpaicg_image_setting_google_size', 'wpaicg_image_setting_google_n',
+                'wpaicg_chat_shortcode_options', 'wpaicg_banned_words', 'wpaicg_banned_ips',
+                'wpaicg_ai_model', 'wpaicg_custom_models', 'wpaicg_google_safety_settings', 'wpaicg_sleep_time',
+                'wpaicg_openai_model_list', 'wpaicg_openrouter_model_list', 'wpaicg_google_model_list',
+                'wpaicg_limit_tokens_form', // Old AI Forms token settings
+                'wpaicg_editor_button_menus',
+                'wpaicg_editor_change_action',
+                'wpaicg_woo_generate_title', 'wpaicg_woo_generate_description', 'wpaicg_woo_generate_short',
+                'wpaicg_woo_generate_tags', 'wpaicg_woo_meta_description', '_wpaicg_shorten_woo_url',
+                'wpaicg_generate_woo_focus_keyword', 'wpaicg_enforce_woo_keyword_in_url', 'wpaicg_woo_custom_prompt',
+                'wpaicg_order_status_token'
+            ];
+            foreach ($old_options as $option_name) {
+                if (get_option($option_name) !== false) {
+                    if (delete_option($option_name)) {
+                        $deleted_counts['options']++;
+                    }
+                }
+            }
+
+            // Drop old settings table
+            $old_table_name = $wpdb->prefix . 'wpaicg';
+            if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $old_table_name)) === $old_table_name) {
+                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+                $wpdb->query("DROP TABLE IF EXISTS " . esc_sql($old_table_name));
+                $deleted_counts['tables']++;
+            }
+
+            // Update category status
+            $this->update_category_status('global_settings', 'deleted');
+
+            wp_send_json_success([
+                /* translators: %1$d is the number of options, %2$d is the number of tables */
+                'message' => sprintf(__('Old global settings deleted: %1$d options and %2$d database tables removed.', 'gpt3-ai-content-generator'), $deleted_counts['options'], $deleted_counts['tables']),
+                'category_status' => 'deleted'
+            ]);
+
+        } catch (Exception $e) {
+            $this->handle_exception($e, 'global_settings_deletion_failed', 'global_settings');
+        }
+    }
+}
--- a/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-indexed-data-action.php
+++ b/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-indexed-data-action.php
@@ -0,0 +1,61 @@
+<?php
+// File: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/gpt3-ai-content-generator/admin/ajax/migration/delete/class-aipkit-delete-old-indexed-data-action.php
+// Status: NEW FILE
+
+namespace WPAICGAdminAjaxMigrationDelete;

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2024-13362
SecRule REQUEST_URI "@contains /wp-admin/admin-ajax.php" 
  "id:20261994,phase:2,deny,status:403,chain,log,msg:'CVE-2024-13362 - Freemius Reflected XSS via url parameter',severity:'CRITICAL',tag:'CVE-2024-13362'"
  SecRule ARGS:url "@rx ^(javascript|data|vbscript|file|about):" 
    "t:none,t:urlDecodeUni,chain"
    SecRule ARGS:action "@streq fs_connect" 
      "t:none"

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School