resolve_post_titles($all_ids); $singular_types = []; foreach (RSVL_Settings_Page::get_public_post_types() as $post_type) { $singular_types[ $post_type->name ] = $post_type->label; } return [ 'is_active' => $is_active, 'display_mode' => $settings['display_mode'], 'model_configured' => $is_active && $settings['chat_model'] !== '' && RSVL_Settings_Page::is_provider_connected($settings['chat_provider']), 'has_training_data' => $is_active && RSVL_Index::has_sources(), 'model_tab_url' => RSVL_Settings_Page::get_settings_tab_url('model'), 'training_tab_url' => RSVL_Settings_Page::get_settings_tab_url('training'), 'display_scope' => $settings['display_scope'], 'singular_types' => $singular_types, 'selected_singular_types' => $settings['display_singular_types'] ?? [], 'archive_options' => $this->get_archive_page_options(), 'selected_archives' => $settings['display_archives'] ?? [], 'special_options' => $this->get_special_page_options(), 'selected_special' => $settings['display_special'] ?? [], 'included_ids' => $included_ids, 'excluded_ids' => $excluded_ids, 'included_tags' => $this->build_page_tags($included_ids, $titles), 'excluded_tags' => $this->build_page_tags($excluded_ids, $titles), ]; } /** * AJAX: Search published posts by title for the visibility include/exclude fields. */ public function ajax_search_pages() { $query = RSVL_Ajax::text('query'); if (mb_strlen($query) < 3) { wp_send_json_success(['results' => []]); } $post_types = get_post_types(['public' => true], 'objects'); // Titles containing the text as typed, as one phrase: a leading "-" // is part of it, not WordPress's "exclude this word". add_filter('wp_query_search_exclusion_prefix', '__return_false'); $posts = get_posts([ 's' => $query, 'search_columns' => ['post_title'], 'sentence' => true, 'post_type' => array_keys($post_types), 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC', 'posts_per_page' => 20, ]); remove_filter('wp_query_search_exclusion_prefix', '__return_false'); $results = []; foreach ($posts as $post) { $type_label = isset($post_types[ $post->post_type ]) ? $post_types[ $post->post_type ]->labels->singular_name : $post->post_type; $results[] = [ 'id' => $post->ID, 'title' => $post->post_title, 'post_type' => $type_label, ]; } wp_send_json_success(['results' => $results]); } /** * Resolve post IDs to titles for the selected-tag display. */ private function resolve_post_titles(array $ids): array { $titles = []; if (empty($ids)) { return $titles; } $posts = get_posts([ 'post__in' => $ids, 'post_type' => 'any', 'post_status' => 'any', 'posts_per_page' => count($ids), ]); foreach ($posts as $post) { $titles[ $post->ID ] = $post->post_title; } return $titles; } /** * Selected page IDs resolved to id/title pairs for the tag widgets. */ private function build_page_tags(array $ids, array $titles): array { $tags = []; foreach ($ids as $id) { if (isset($titles[ $id ])) { $tags[] = ['id' => $id, 'title' => $titles[ $id ]]; } } return $tags; } private function get_archive_page_options(): array { $options = [ 'category' => __('Category archives', 'resolver-ai-chatbot'), 'tag' => __('Tag archives', 'resolver-ai-chatbot'), 'author' => __('Author archives', 'resolver-ai-chatbot'), 'date' => __('Date archives', 'resolver-ai-chatbot'), ]; $taxonomies = get_taxonomies(['public' => true, 'show_ui' => true], 'objects'); unset($taxonomies['category'], $taxonomies['post_tag'], $taxonomies['post_format']); foreach ($taxonomies as $taxonomy) { /* translators: %s: taxonomy singular name */ $options[ $taxonomy->name ] = sprintf(__('%s archives', 'resolver-ai-chatbot'), $taxonomy->labels->singular_name); } return $options; } private function get_special_page_options(): array { return [ 'home' => __('Blog (Posts page)', 'resolver-ai-chatbot'), 'search' => __('Search results', 'resolver-ai-chatbot'), '404' => __('404 page', 'resolver-ai-chatbot'), ]; } /** Register the AJAX endpoint. */ public static function register(): void { RSVL_Ajax::register(RSVL_Ajax::admin_routes([ 'resolver_search_pages' => 'ajax_search_pages', ]), new self()); } }