'Connector ID and Project ID are required.')); } $type = isset($_POST['type']) ? sanitize_text_field(wp_unslash($_POST['type'])) : ''; if ($type === 'check_configuration') { // Handle the "check_configuration" type $response = array(); $data = array( 'connectorId' => $connector_id, 'projectId' => $project_id, 'type' => $type ); $api_response = wp_remote_post(AI12Z_CONNECTOR_API . $connector_id . '/validate?projectid=' . $project_id, array( 'body' => wp_json_encode($data), 'headers' => array( 'Content-Type' => 'application/json' ) )); $response_code = wp_remote_retrieve_response_code( $api_response ); if ($response_code !== 200) { ai12z_log('Error: ' . $api_response->get_error_message()); $response[] = array( 'status' => 'Error', 'message' => $api_response->get_error_message() ); } else { $response[] = array( 'status' => 'Success', 'message' => 'Configuration is Ok.' ); } wp_send_json($response); } else { // Initial sync. Processed one page at a time so large sites (1000+ items) // don't exhaust PHP's max_execution_time / memory in a single request. // The browser drives the paging and pauses between pages (see js/ajax.js). ai12z_handle_initial_sync($connector_id, $project_id, $type); } } add_action('wp_ajax_ai12z_post_action', 'custom_ajax_handler'); // Read the requested post_types from the POST body, sanitize them, and keep // only the ones that are actually registered as public content types. function ai12z_resolve_selected_types() { $selected_types = array(); if (isset($_POST['post_types'])) { $raw_types = wp_unslash($_POST['post_types']); if (!is_array($raw_types)) { $raw_types = array($raw_types); } foreach ($raw_types as $raw_type) { $selected_types[] = sanitize_text_field($raw_type); } } $allowed_types = get_post_types(array('public' => true)); return array_values(array_intersect($selected_types, $allowed_types)); } // Process a single page of the initial sync and return per-item results. function ai12z_handle_initial_sync($connector_id, $project_id, $type) { // Only the content types the user selected are synced. $selected_types = ai12z_resolve_selected_types(); if (empty($selected_types)) { wp_send_json_error(array('message' => 'Please select at least one content type to sync.')); } // Paging parameters. $per_page = isset($_POST['per_page']) ? intval($_POST['per_page']) : 25; if ($per_page < 1) { $per_page = 25; } if ($per_page > 100) { $per_page = 100; } $page = isset($_POST['page']) ? intval($_POST['page']) : 0; if ($page < 0) { $page = 0; } // Total published items across the selected types (for the progress bar). $total = 0; foreach ($selected_types as $selected_type) { $counts = wp_count_posts($selected_type); if (isset($counts->publish)) { $total += intval($counts->publish); } } $posts = get_posts(array( 'post_type' => $selected_types, 'post_status' => 'publish', 'numberposts' => $per_page, 'offset' => $page * $per_page, 'orderby' => 'ID', 'order' => 'ASC', )); $items = array(); foreach ($posts as $post) { // Send the full extracted text content (post body + custom-field text), // minus any fields excluded for this content type. $exclusions = ai12z_get_type_exclusions($post->post_type); $content = get_full_post_text_content( $post->ID, 'html', $exclusions ) ?: ""; // Prepare data to send $data = array( 'post_id' => $post->ID, 'post_status' => $post->post_status, 'post_title' => $post->post_title, "post_type" => $post->post_type, "post_content" => $content, "post_description" => $post->post_excerpt, "post_metadata" => get_post_meta($post->ID), 'link' => get_permalink($post->ID), 'connectorId' => $connector_id, 'projectId' => $project_id, 'type' => $type ); // Send data to external service $api_response = wp_remote_post(AI12Z_CONNECTOR_API . $connector_id . '?projectid=' . $project_id, array( 'body' => wp_json_encode($data), 'headers' => array( 'Content-Type' => 'application/json' ), 'timeout' => 30 )); if (is_wp_error($api_response)) { $items[] = array( 'post_id' => $post->ID, 'page' => $post->post_title, 'post_type' => $post->post_type, 'status' => 'Error', 'message' => $api_response->get_error_message() ); } else { $response_code = wp_remote_retrieve_response_code($api_response); if ($response_code >= 200 && $response_code < 300) { $items[] = array( 'post_id' => $post->ID, 'page' => $post->post_title, 'post_type' => $post->post_type, 'status' => 'Success', 'message' => 'Data sent successfully.' ); } else { $items[] = array( 'post_id' => $post->ID, 'page' => $post->post_title, 'post_type' => $post->post_type, 'status' => 'Error', 'message' => 'Server responded with HTTP ' . $response_code . '.' ); } } } // A full page means there is (probably) more to fetch. A short/empty page // means we've reached the end. $has_more = (count($posts) === $per_page); wp_send_json_success(array( 'items' => $items, 'page' => $page, 'per_page' => $per_page, 'total' => $total, 'has_more' => $has_more, )); } // Build the connector-config settings API URL for a project (app.ai12z.net). function ai12z_connector_settings_url($project_id) { return AI12Z_API . 'api/projects/' . rawurlencode($project_id) . '/connectorstore/config/settings'; } // GET the current connector config settings from ai12z so the settings screen // can pre-check the saved content types on load. function ai12z_get_connector_settings_handler() { check_ajax_referer('custom_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Permission denied.'), 403); } $api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : ''; $project_id = isset($_POST['project_id']) ? sanitize_text_field(wp_unslash($_POST['project_id'])) : ''; // Nothing to look up without credentials — report "not found" so the UI stays quiet. if (empty($api_key) || empty($project_id)) { wp_send_json_success(array('found' => false, 'message' => 'API Key and Agent Id are required.')); } $response = wp_remote_get(ai12z_connector_settings_url($project_id), array( 'headers' => array( 'x-api-key' => $api_key, 'Accept' => 'application/json', ), 'timeout' => 20, )); if (is_wp_error($response)) { ai12z_log('Get connector settings failed: ' . $response->get_error_message()); wp_send_json_success(array('found' => false, 'message' => $response->get_error_message())); } $code = wp_remote_retrieve_response_code($response); if ($code < 200 || $code >= 300) { wp_send_json_success(array('found' => false, 'code' => $code)); } $body = json_decode(wp_remote_retrieve_body($response), true); $settings = (is_array($body) && isset($body['settings']) && is_array($body['settings'])) ? $body['settings'] : array(); // The WordPress connector stores the selected content types (and each custom // type's field mapping) under "customTypes" (fall back to "contentTypes"). $custom_types = array(); if (isset($settings['customTypes']) && is_array($settings['customTypes'])) { $custom_types = $settings['customTypes']; } elseif (isset($settings['contentTypes']) && is_array($settings['contentTypes'])) { $custom_types = $settings['contentTypes']; } // Keep the local copy (used by the webhook and initial sync) in step with the // connector so field mappings apply without another API round-trip. update_option('ai12z_content_types', ai12z_sanitize_content_types_map($custom_types)); wp_send_json_success(array( 'found' => true, 'customTypes' => $custom_types, )); } add_action('wp_ajax_ai12z_get_settings', 'ai12z_get_connector_settings_handler'); // Save the WordPress connector options and push the selected content types to // the ai12z connector config settings. Initial Sync is gated on this succeeding. function ai12z_save_connector_settings_handler() { check_ajax_referer('custom_ajax_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Permission denied.'), 403); } $api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : ''; $connector_id = isset($_POST['connector_id']) ? sanitize_text_field(wp_unslash($_POST['connector_id'])) : ''; $project_id = isset($_POST['project_id']) ? sanitize_text_field(wp_unslash($_POST['project_id'])) : ''; $control_version = isset($_POST['control_version']) ? sanitize_text_field(wp_unslash($_POST['control_version'])) : ''; // Persist the WordPress-side options first (these drive the realtime webhook). update_option('ai12z_api_key', $api_key); update_option('ai12z_connector_id', $connector_id); update_option('ai12z_project_id', $project_id); update_option('ai12z_control_version', $control_version); if (empty($api_key) || empty($project_id)) { wp_send_json_error(array('message' => 'API Key and Agent Id are required to save connector settings.')); } // The full content-types map: selected types with each custom type's field // mapping, e.g. { "post": "", "product": { "title": [...], "content": [...] } }. $raw_map = array(); if (isset($_POST['content_types_map'])) { $decoded = json_decode(wp_unslash($_POST['content_types_map']), true); if (is_array($decoded)) { $raw_map = $decoded; } } // Fall back to a flat selection if only post_types was sent. if (empty($raw_map)) { foreach (ai12z_resolve_selected_types() as $selected_type) { $raw_map[$selected_type] = ''; } } $content_types = ai12z_sanitize_content_types_map($raw_map); // Mirror locally so the webhook and initial sync can apply the mapping. update_option('ai12z_content_types', $content_types); $payload = array( 'settings' => array( // Stored as "customTypes" by the connector. Cast to object so an empty // selection serializes as {} rather than []. 'customTypes' => (object) $content_types, ), ); $response = wp_remote_post(ai12z_connector_settings_url($project_id), array( 'headers' => array( 'Content-Type' => 'application/json', 'x-api-key' => $api_key, ), 'body' => wp_json_encode($payload), 'timeout' => 30, )); if (is_wp_error($response)) { ai12z_log('Save connector settings failed: ' . $response->get_error_message()); wp_send_json_error(array('message' => $response->get_error_message())); } $code = wp_remote_retrieve_response_code($response); $body = json_decode(wp_remote_retrieve_body($response), true); if ($code < 200 || $code >= 300) { $message = (is_array($body) && isset($body['error'])) ? $body['error'] : ('Server responded with HTTP ' . $code . '.'); wp_send_json_error(array('message' => $message, 'code' => $code)); } wp_send_json_success(array( 'message' => 'Settings saved and synced to ai12z.', 'customTypes' => (object) $content_types, )); } add_action('wp_ajax_ai12z_save_settings', 'ai12z_save_connector_settings_handler');