* and footer respectively (see rapidtextai_enqueue_admin_app), so the * React bundle is not emitted as stray tags inside the page content. * * @param array $config Page-level configuration passed to the React app. */ function rapidtextai_react_app_root( $config = array() ) { $config = array_merge( array( 'page' => 'settings', 'rest_url' => esc_url_raw( rest_url( 'rapidtextai/v1' ) ), 'rest_nonce' => wp_create_nonce( 'wp_rest' ), 'ajax_url' => admin_url( 'admin-ajax.php' ), 'ajax_nonce' => wp_create_nonce( 'rapidtextai_assistant_nonce' ), 'app_url' => 'https://app.rapidtextai.com', 'data' => new stdClass(), ), $config ); ?>
' . "\n"; } return $tag; } /** * Shared permission callback — only users who can manage options may use these routes. */ function rapidtextai_rest_permission() { return current_user_can( 'manage_options' ); } /** * Register all REST routes. */ add_action( 'rest_api_init', function () { $ns = 'rapidtextai/v1'; // ─────────────────────────────────────────────────────────── Settings register_rest_route( $ns, '/settings', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function () { $api_key = get_option( 'rapidtextai_api_key', '' ); return rest_ensure_response( array( 'api_key' => $api_key, 'api_key_set' => ! empty( $api_key ), ) ); }, ) ); register_rest_route( $ns, '/settings/api-key', array( 'methods' => 'POST', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $api_key = sanitize_text_field( (string) $request->get_param( 'api_key' ) ); if ( '' === $api_key ) { return new WP_Error( 'missing_key', 'API key is required.', array( 'status' => 400 ) ); } update_option( 'rapidtextai_api_key', $api_key ); // Bind this site to the account that owns the key. A failure here // must not undo the key that was just saved - the site still works, // it is just not linked yet - so it is reported, not thrown. $link_note = ''; if ( function_exists( 'rapidtextai_connect_link_site_for_key' ) ) { $linked = rapidtextai_connect_link_site_for_key( $api_key ); if ( is_wp_error( $linked ) ) { $link_note = $linked->get_error_message(); } } return rest_ensure_response( array( 'message' => 'API Key saved successfully.', 'linked' => ( '' === $link_note ), 'link_note' => $link_note, ) ); }, ) ); // ─────────────────────────────────────────────────────────── Account proxy register_rest_route( $ns, '/account', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function () { $api_key = get_option( 'rapidtextai_api_key', '' ); if ( empty( $api_key ) ) { return new WP_Error( 'no_key', 'API key is not configured.', array( 'status' => 400 ) ); } $url = rapidtextai_api_origin() . '/api.php?gigsixkey=' . urlencode( $api_key ); $response = wp_remote_get( $url, array( 'timeout' => 30, 'sslverify' => false ) ); if ( is_wp_error( $response ) ) { return new WP_Error( 'request_failed', $response->get_error_message(), array( 'status' => 502 ) ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( json_last_error() !== JSON_ERROR_NONE ) { return new WP_Error( 'invalid_response', 'Invalid response from RapidTextAI.', array( 'status' => 502 ) ); } return rest_ensure_response( $data ); }, ) ); // ─────────────────────────────────────────────────────────── Options (authors + categories) register_rest_route( $ns, '/options', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function () { $authors = get_users( array( 'role__in' => array( 'administrator', 'editor', 'author' ), 'fields' => array( 'ID', 'display_name' ), ) ); $categories = get_categories( array( 'hide_empty' => 0 ) ); $author_list = array_map( function ( $u ) { return array( 'id' => (int) $u->ID, 'name' => $u->display_name ); }, $authors ); $category_list = array_map( function ( $c ) { return array( 'id' => (int) $c->term_id, 'name' => $c->name ); }, $categories ); return rest_ensure_response( array( 'authors' => array_values( $author_list ), 'categories' => array_values( $category_list ), ) ); }, ) ); // ─────────────────────────────────────────────────────────── Models proxy (chatbots) register_rest_route( $ns, '/models', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function () { $api_key = get_option( 'rapidtextai_api_key', '' ); if ( empty( $api_key ) ) { return new WP_Error( 'no_key', 'API key is not configured.', array( 'status' => 400 ) ); } $response = wp_remote_get( 'https://app.rapidtextai.com/openai/v1/models?gigsixkey=' . urlencode( $api_key ), array( 'timeout' => 30 ) ); if ( is_wp_error( $response ) ) { return new WP_Error( 'request_failed', 'Failed to fetch models.', array( 'status' => 502 ) ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( isset( $data['data'] ) ) { return rest_ensure_response( $data['data'] ); } return new WP_Error( 'invalid_response', 'Invalid response format.', array( 'status' => 502 ) ); }, ) ); // ─────────────────────────────────────────────────────────── Campaigns register_rest_route( $ns, '/campaigns', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function () { $campaigns = get_option( 'rapidtextai_auto_blogging_campaigns', array() ); $out = array(); foreach ( $campaigns as $id => $campaign ) { $topics = array_filter( array_map( 'trim', explode( "\n", isset( $campaign['topics'] ) ? $campaign['topics'] : '' ) ) ); $campaign['topics_count'] = count( $topics ); $campaign['next_run'] = wp_next_scheduled( 'rapidtextai_auto_blogging_cron_' . $id ); $out[] = $campaign; } return rest_ensure_response( $out ); }, ) ); register_rest_route( $ns, '/campaigns/(?P[a-zA-Z0-9_\-]+)', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $id = sanitize_text_field( $request['id'] ); $campaigns = get_option( 'rapidtextai_auto_blogging_campaigns', array() ); if ( ! isset( $campaigns[ $id ] ) ) { return new WP_Error( 'not_found', 'Campaign not found.', array( 'status' => 404 ) ); } return rest_ensure_response( $campaigns[ $id ] ); }, ) ); register_rest_route( $ns, '/campaigns', array( 'methods' => 'POST', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $params = $request->get_json_params(); if ( ! is_array( $params ) ) { $params = $request->get_params(); } $result = rapidtextai_rest_save_campaign( $params ); if ( is_wp_error( $result ) ) { return $result; } return rest_ensure_response( $result ); }, ) ); register_rest_route( $ns, '/campaigns/(?P[a-zA-Z0-9_\-]+)/toggle', array( 'methods' => 'POST', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $id = sanitize_text_field( $request['id'] ); $campaigns = get_option( 'rapidtextai_auto_blogging_campaigns', array() ); if ( ! isset( $campaigns[ $id ] ) ) { return new WP_Error( 'not_found', 'Campaign not found.', array( 'status' => 404 ) ); } $campaigns[ $id ]['enabled'] = empty( $campaigns[ $id ]['enabled'] ) ? 1 : 0; update_option( 'rapidtextai_auto_blogging_campaigns', $campaigns ); if ( $campaigns[ $id ]['enabled'] ) { rapidtextai_schedule_campaign_cron( $id, $campaigns[ $id ] ); } else { wp_clear_scheduled_hook( 'rapidtextai_auto_blogging_cron_' . $id ); } return rest_ensure_response( array( 'enabled' => (bool) $campaigns[ $id ]['enabled'], 'message' => $campaigns[ $id ]['enabled'] ? 'Campaign enabled successfully.' : 'Campaign disabled successfully.', ) ); }, ) ); register_rest_route( $ns, '/campaigns/(?P[a-zA-Z0-9_\-]+)', array( 'methods' => 'DELETE', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $id = sanitize_text_field( $request['id'] ); $campaigns = get_option( 'rapidtextai_auto_blogging_campaigns', array() ); if ( ! isset( $campaigns[ $id ] ) ) { return new WP_Error( 'not_found', 'Campaign not found.', array( 'status' => 404 ) ); } wp_clear_scheduled_hook( 'rapidtextai_auto_blogging_cron_' . $id ); unset( $campaigns[ $id ] ); update_option( 'rapidtextai_auto_blogging_campaigns', $campaigns ); return rest_ensure_response( array( 'message' => 'Campaign deleted successfully.' ) ); }, ) ); register_rest_route( $ns, '/campaigns/(?P[a-zA-Z0-9_\-]+)/generate', array( 'methods' => 'POST', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $id = sanitize_text_field( $request['id'] ); $campaigns = get_option( 'rapidtextai_auto_blogging_campaigns', array() ); if ( ! isset( $campaigns[ $id ] ) ) { return new WP_Error( 'not_found', 'Campaign not found.', array( 'status' => 404 ) ); } rapidtextai_generate_auto_blog_post( $id ); return rest_ensure_response( array( 'message' => 'Post generation triggered for selected campaign.' ) ); }, ) ); // ─────────────────────────────────────────────────────────── Improve topics register_rest_route( $ns, '/improve-topics', array( 'methods' => 'POST', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $topics = sanitize_textarea_field( (string) $request->get_param( 'topics' ) ); $topics_array = array_filter( array_map( 'trim', explode( "\n", $topics ) ) ); if ( empty( $topics_array ) ) { return new WP_Error( 'no_topics', 'No topics provided.', array( 'status' => 400 ) ); } if ( count( $topics_array ) > 50 ) { $topics_array = array_slice( $topics_array, 0, 50 ); } $api_key = get_option( 'rapidtextai_api_key', '' ); if ( empty( $api_key ) ) { return new WP_Error( 'no_key', 'API key not found. Please set up your RapidTextAI authentication.', array( 'status' => 400 ) ); } $prompt = "I have a list of blog post topics that need improvement to make them more specific, engaging, and SEO-friendly. Please improve each of these topics:\n\n"; $prompt .= implode( "\n", $topics_array ); $prompt .= "\n\nIMPORTANT FORMATTING RULES:\n"; $prompt .= "1. Each improved topic MUST be on a SINGLE LINE (no line breaks within a topic)\n"; $prompt .= "2. Use semicolons (;) to separate different parts within the same topic\n"; $prompt .= "3. Separate different topics with a blank line (double newline)\n"; $prompt .= "4. Include: Topic, Keywords, Tone, Audience, Length, and CTA\n"; $prompt .= "5. Use the same language as the input topics\n\n"; $prompt .= "FORMAT EXAMPLE:\n"; $prompt .= "Topic: Complete Guide to Sustainable Gardening for Beginners; Keywords: sustainable gardening, eco-friendly plants, organic fertilizer, water conservation, composting methods; Tone: friendly and informative; Audience: homeowners and gardening beginners; Length: 2500-3000 words; CTA: Download our free sustainable gardening checklist\n\n"; $prompt .= "Topic: Another Topic Here; Keywords: keyword1, keyword2, keyword3; Tone: professional; Audience: business owners; Length: 1500-2000 words; CTA: Contact us for consultation\n\n"; $prompt .= "Return each improved topic on ONE SINGLE LINE, separated by blank lines. Do NOT add line breaks within a topic."; $response = wp_remote_post( 'https://app.rapidtextai.com/openai/v1/chat/completions?gigsixkey=' . urlencode( $api_key ), array( 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ), 'body' => wp_json_encode( array( 'model' => 'gpt-3.5-turbo', 'messages' => array( array( 'role' => 'system', 'content' => 'You are a helpful assistant that improves blog post topics.' ), array( 'role' => 'user', 'content' => $prompt ), ), 'max_tokens' => 500, 'temperature' => 0.7, ) ), 'timeout' => 30, ) ); if ( is_wp_error( $response ) ) { return new WP_Error( 'request_failed', 'Error connecting to RapidTextAI: ' . $response->get_error_message(), array( 'status' => 502 ) ); } $http_code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); if ( 200 !== $http_code ) { return new WP_Error( 'api_failed', 'API request failed with code ' . $http_code, array( 'status' => 502 ) ); } $response_data = json_decode( $body, true ); if ( ! isset( $response_data['choices'][0]['message']['content'] ) ) { return new WP_Error( 'invalid_response', 'Invalid response from API', array( 'status' => 502 ) ); } $improved_topics = trim( $response_data['choices'][0]['message']['content'] ); $improved_topics = preg_replace( '/^\d+\.\s+/m', '', $improved_topics ); $improved_topics = preg_replace( '/^- /m', '', $improved_topics ); return rest_ensure_response( array( 'message' => 'Topics improved successfully!', 'improved_topics' => $improved_topics, ) ); }, ) ); // ─────────────────────────────────────────────────────────── Logs register_rest_route( $ns, '/logs', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function () { return rest_ensure_response( array( 'logs' => rapidtextai_read_error_logs() ) ); }, ) ); register_rest_route( $ns, '/logs', array( 'methods' => 'DELETE', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function () { if ( rapidtextai_clear_error_logs() ) { return rest_ensure_response( array( 'message' => 'Logs cleared successfully.' ) ); } return new WP_Error( 'clear_failed', 'Failed to clear logs.', array( 'status' => 500 ) ); }, ) ); // ─────────────────────────────────────────────────────────── Chatbots register_rest_route( $ns, '/chatbots', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function () { $rows = rapidtextai_get_all_chatbots(); $out = array(); foreach ( $rows as $row ) { $out[] = rapidtextai_rest_prepare_chatbot( $row ); } return rest_ensure_response( $out ); }, ) ); register_rest_route( $ns, '/chatbots/(?P\d+)', array( 'methods' => 'GET', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $chatbot = rapidtextai_get_chatbot( (int) $request['id'] ); if ( ! $chatbot ) { return new WP_Error( 'not_found', 'Chatbot not found.', array( 'status' => 404 ) ); } return rest_ensure_response( rapidtextai_rest_prepare_chatbot( $chatbot ) ); }, ) ); register_rest_route( $ns, '/chatbots', array( 'methods' => 'POST', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { $chatbot_id = (int) $request->get_param( 'id' ); $params = $request->get_json_params(); if ( ! is_array( $params ) ) { $params = $request->get_params(); } // Accept either a nested `settings` object or flat fields. if ( isset( $params['settings'] ) && is_array( $params['settings'] ) ) { $params = array_merge( $params, $params['settings'] ); unset( $params['settings'] ); } $data = array( 'name' => isset( $params['name'] ) ? sanitize_text_field( $params['name'] ) : '', 'description' => isset( $params['description'] ) ? sanitize_textarea_field( $params['description'] ) : '', 'model' => isset( $params['model'] ) ? sanitize_text_field( $params['model'] ) : 'gpt-3.5-turbo', 'theme' => isset( $params['theme'] ) ? sanitize_text_field( $params['theme'] ) : 'modern', 'status' => isset( $params['status'] ) ? sanitize_text_field( $params['status'] ) : 'active', 'system_message' => isset( $params['system_message'] ) ? sanitize_textarea_field( $params['system_message'] ) : '', 'welcome_message' => isset( $params['welcome_message'] ) ? sanitize_textarea_field( $params['welcome_message'] ) : '', 'primary_color' => isset( $params['primary_color'] ) ? sanitize_hex_color( $params['primary_color'] ) : '#007cba', 'secondary_color' => isset( $params['secondary_color'] ) ? sanitize_hex_color( $params['secondary_color'] ) : '#005177', 'text_color' => isset( $params['text_color'] ) ? sanitize_hex_color( $params['text_color'] ) : '#ffffff', 'background_color' => isset( $params['background_color'] ) ? sanitize_hex_color( $params['background_color'] ) : '#f9f9f9', 'position' => isset( $params['position'] ) ? sanitize_text_field( $params['position'] ) : 'bottom-right', 'size' => isset( $params['size'] ) ? sanitize_text_field( $params['size'] ) : 'medium', 'auto_open' => ! empty( $params['auto_open'] ) ? 1 : 0, 'auto_open_delay' => isset( $params['auto_open_delay'] ) ? intval( $params['auto_open_delay'] ) : 3000, 'show_avatar' => ! empty( $params['show_avatar'] ) ? 1 : 0, 'avatar_url' => isset( $params['avatar_url'] ) ? esc_url_raw( $params['avatar_url'] ) : '', 'max_tokens' => isset( $params['max_tokens'] ) ? intval( $params['max_tokens'] ) : 2000, 'temperature' => isset( $params['temperature'] ) ? floatval( $params['temperature'] ) : 0.7, 'knowledge_base' => isset( $params['knowledge_base'] ) && is_array( $params['knowledge_base'] ) ? $params['knowledge_base'] : array(), 'tools' => isset( $params['tools'] ) && is_array( $params['tools'] ) ? $params['tools'] : array(), ); // rapidtextai_save_chatbot() treats checkbox presence via isset(); // remove keys when false so they are stored as 0 correctly. if ( empty( $data['auto_open'] ) ) { unset( $data['auto_open'] ); } if ( empty( $data['show_avatar'] ) ) { unset( $data['show_avatar'] ); } $result = rapidtextai_save_chatbot( $data, $chatbot_id ); if ( ! $result ) { return new WP_Error( 'save_failed', 'Error saving chatbot.', array( 'status' => 500 ) ); } return rest_ensure_response( array( 'id' => (int) $result, 'message' => 'Chatbot saved successfully!', ) ); }, ) ); register_rest_route( $ns, '/chatbots/(?P\d+)', array( 'methods' => 'DELETE', 'permission_callback' => 'rapidtextai_rest_permission', 'callback' => function ( WP_REST_Request $request ) { rapidtextai_chatbots_delete( (int) $request['id'] ); return rest_ensure_response( array( 'message' => 'Chatbot deleted successfully.' ) ); }, ) ); } ); /** * Sanitize and persist a campaign (create or update) from request params. * * @param array $params Request parameters. * @return array|WP_Error */ function rapidtextai_rest_save_campaign( $params ) { $campaigns = get_option( 'rapidtextai_auto_blogging_campaigns', array() ); $existing_id = isset( $params['id'] ) && ! empty( $params['id'] ) ? sanitize_text_field( $params['id'] ) : ''; $is_new = empty( $existing_id ); $campaign_id = $existing_id; if ( $is_new ) { $campaign_id = 'campaign_' . time(); $i = 0; while ( isset( $campaigns[ $campaign_id ] ) && $i < 100 ) { $campaign_id = 'campaign_' . time() . '_' . wp_rand( 100, 999 ); $i++; } } $campaign = array( 'id' => $campaign_id, 'name' => isset( $params['name'] ) ? sanitize_text_field( $params['name'] ) : '', 'enabled' => ! empty( $params['enabled'] ) ? 1 : 0, 'schedule' => isset( $params['schedule'] ) ? sanitize_text_field( $params['schedule'] ) : 'daily', 'post_status' => isset( $params['post_status'] ) ? sanitize_text_field( $params['post_status'] ) : 'draft', 'post_author' => isset( $params['post_author'] ) ? intval( $params['post_author'] ) : get_current_user_id(), 'topics' => isset( $params['topics'] ) ? sanitize_textarea_field( $params['topics'] ) : '', 'model' => isset( $params['model'] ) ? sanitize_text_field( $params['model'] ) : 'gpt-3.5-turbo', 'tone' => isset( $params['tone'] ) ? sanitize_text_field( $params['tone'] ) : 'informative', 'post_category' => isset( $params['post_category'] ) && is_array( $params['post_category'] ) ? array_map( 'intval', $params['post_category'] ) : array(), 'generate_tags' => ! empty( $params['generate_tags'] ) ? 1 : 0, 'tags_count' => isset( $params['tags_count'] ) ? intval( $params['tags_count'] ) : 5, 'excerpt_length' => isset( $params['excerpt_length'] ) ? intval( $params['excerpt_length'] ) : 55, 'taxonomy_limit' => isset( $params['taxonomy_limit'] ) ? intval( $params['taxonomy_limit'] ) : 5, 'include_images' => ! empty( $params['include_images'] ) ? 1 : 0, 'include_featured_image' => ! empty( $params['include_featured_image'] ) ? 1 : 0, 'max_images' => isset( $params['max_images'] ) ? intval( $params['max_images'] ) : 5, 'enable_logging' => ! empty( $params['enable_logging'] ) ? 1 : 0, ); if ( $is_new ) { $campaign['created'] = current_time( 'mysql' ); } elseif ( isset( $campaigns[ $campaign_id ]['created'] ) ) { $campaign['created'] = $campaigns[ $campaign_id ]['created']; } $campaign['updated'] = current_time( 'mysql' ); $campaigns[ $campaign_id ] = $campaign; update_option( 'rapidtextai_auto_blogging_campaigns', $campaigns ); wp_clear_scheduled_hook( 'rapidtextai_auto_blogging_cron_' . $campaign_id ); if ( $campaign['enabled'] ) { rapidtextai_schedule_campaign_cron( $campaign_id, $campaign ); } // Maintain backward compatibility with the old option. $all = get_option( 'rapidtextai_auto_blogging_campaigns', array() ); if ( count( $all ) === 1 || false !== strpos( $campaign['name'], 'Default' ) ) { update_option( 'rapidtextai_auto_blogging', $campaign ); } return array( 'id' => $campaign_id, 'message' => $is_new ? 'Campaign created successfully.' : 'Campaign updated successfully.', ); } /** * Normalize a chatbot row (decode JSON/serialized fields) for REST output. * * @param array $row Raw DB row. * @return array */ function rapidtextai_rest_prepare_chatbot( $row ) { // The single-chatbot path (rapidtextai_get_chatbot) already json_decodes // `settings`, while the list path (rapidtextai_get_all_chatbots) returns // the raw JSON string — so handle both cases here. $settings = isset( $row['settings'] ) ? $row['settings'] : array(); if ( is_string( $settings ) ) { $decoded = json_decode( $settings, true ); $settings = is_array( $decoded ) ? $decoded : array(); } elseif ( ! is_array( $settings ) ) { $settings = array(); } $row['settings'] = $settings; $kb = maybe_unserialize( isset( $row['knowledge_base'] ) ? $row['knowledge_base'] : '' ); $row['knowledge_base'] = is_array( $kb ) ? $kb : array(); $tools = maybe_unserialize( isset( $row['tools'] ) ? $row['tools'] : '' ); $row['tools'] = is_array( $tools ) ? $tools : array(); $row['id'] = (int) $row['id']; $row['shortcode'] = '[rapidtextai_chatbot id="' . $row['id'] . '"]'; return $row; }