service_client = new AI_Search_Service(); } /** * Display main settings page with tabs */ public function display_settings_page() { // Check if setup wizard should be shown if ( isset( $_GET['setup'] ) && $_GET['setup'] === 'wizard' ) { $wizard = new AI_Search_Setup_Wizard(); $wizard->display_wizard_page(); return; } $active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( $_GET['tab'] ) : 'general'; echo '
'; echo '

AI Search Settings

'; $this->display_tab_navigation( $active_tab ); $this->display_tab_content( $active_tab ); echo '
'; } /** * Display tab navigation */ private function display_tab_navigation( $active_tab ) { echo ''; } /** * Display tab content */ private function display_tab_content( $active_tab ) { switch ( $active_tab ) { case 'general': include plugin_dir_path( __FILE__ ) . 'views/settings-general.php'; break; case 'embeddings': include plugin_dir_path( __FILE__ ) . 'views/settings-embeddings.php'; break; case 'custom-fields': include plugin_dir_path( __FILE__ ) . 'views/settings-custom-fields.php'; break; case 'woocommerce': if ( class_exists( 'WooCommerce' ) ) { include plugin_dir_path( __FILE__ ) . 'views/settings-woocommerce.php'; } break; case 'cache': include plugin_dir_path( __FILE__ ) . 'views/settings-cache.php'; break; } } /** * Generate embedding for selected custom post type. */ public function generate_selected_embeddings() { if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['cpt'] ) ) { wp_die( __( 'Unauthorized access', 'ai-search' ) ); } $cpt = sanitize_text_field( $_POST['cpt'] ); $limit = isset( $_POST['limit'] ) ? intval( $_POST['limit'] ) : 50; $limit = min( max( $limit, 1 ), 1000 ); $processed_titles = []; $posts = get_posts([ 'numberposts' => $limit, 'post_type' => $cpt, 'post_status' => 'publish', 'meta_query' => [ [ 'key' => '_ai_search_embedding', 'compare' => 'NOT EXISTS' ] ] ]); $ai_search = AI_Search::get_instance(); foreach ( $posts as $post ) { $ai_search->generate_embedding( $post->ID ); $processed_titles[] = $post->post_title; } set_transient( 'ai_search_processed_titles', $processed_titles, MINUTE_IN_SECONDS ); wp_redirect( admin_url( 'options-general.php?page=ai-search&embeddings_generated=true&tab=embeddings' ) ); exit; } /** * Clear all embedding cache (transients and post meta). */ public function clear_embeddings_cache() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'Unauthorized access', 'ai-search' ) ); } check_admin_referer( 'ai_search_clear_cache' ); $cache_type = isset( $_POST['cache_type'] ) ? sanitize_text_field( $_POST['cache_type'] ) : 'transients'; $cleared_count = 0; if ( $cache_type === 'transients' || $cache_type === 'all' ) { $cleared_count += $this->clear_embedding_transients(); } if ( $cache_type === 'post_meta' || $cache_type === 'all' ) { $cleared_count += $this->clear_post_meta_embeddings(); } $message = $cache_type === 'all' ? 'all_cache_cleared' : $cache_type . '_cleared'; wp_redirect( admin_url( 'options-general.php?page=ai-search&cache_cleared=' . $message . '&count=' . $cleared_count . '&tab=cache' ) ); exit; } /** * Validate the service token via admin action. */ public function validate_service_token() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'Unauthorized access', 'ai-search' ) ); } check_admin_referer( 'ai_search_validate_token' ); $service_token = get_option( 'ai_search_service_token', '' ); if ( empty( $service_token ) ) { wp_redirect( admin_url( 'options-general.php?page=ai-search&token_validation=no_token&tab=general' ) ); exit; } $validation_result = $this->service_client->validate_token(); if ( $validation_result === false ) { wp_redirect( admin_url( 'options-general.php?page=ai-search&token_validation=error&tab=general' ) ); } elseif ( isset( $validation_result['valid'] ) && $validation_result['valid'] === true ) { set_transient( 'ai_search_validation_data', $validation_result, 300 ); wp_redirect( admin_url( 'options-general.php?page=ai-search&token_validation=success&tab=general' ) ); } else { set_transient( 'ai_search_validation_error', $validation_result, 300 ); wp_redirect( admin_url( 'options-general.php?page=ai-search&token_validation=invalid&tab=general' ) ); } exit; } /** * Save custom fields settings. */ public function save_custom_fields_settings() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'Unauthorized access', 'ai-search' ) ); } check_admin_referer( 'ai_search_save_custom_fields' ); $custom_fields = isset( $_POST['custom_fields'] ) ? $_POST['custom_fields'] : []; $post_types = get_post_types( [ 'public' => true ] ); foreach ( $post_types as $post_type ) { if ( isset( $custom_fields[ $post_type ] ) && is_array( $custom_fields[ $post_type ] ) ) { $selected_fields = array_map( 'sanitize_text_field', $custom_fields[ $post_type ] ); update_option( 'ai_search_custom_fields_' . $post_type, $selected_fields ); } else { delete_option( 'ai_search_custom_fields_' . $post_type ); } } wp_redirect( admin_url( 'options-general.php?page=ai-search&custom_fields_saved=true&tab=custom-fields' ) ); exit; } /** * Save WooCommerce fields settings. */ public function save_woocommerce_fields_settings() { if ( ! current_user_can( 'manage_options' ) || ! class_exists( 'WooCommerce' ) ) { wp_die( __( 'Unauthorized access', 'ai-search' ) ); } check_admin_referer( 'ai_search_save_woocommerce_fields' ); $woocommerce_fields = isset( $_POST['woocommerce_fields'] ) ? $_POST['woocommerce_fields'] : []; if ( ! empty( $woocommerce_fields ) && is_array( $woocommerce_fields ) ) { $selected_fields = array_map( 'sanitize_text_field', $woocommerce_fields ); update_option( 'ai_search_woocommerce_fields', $selected_fields ); } else { delete_option( 'ai_search_woocommerce_fields' ); } wp_redirect( admin_url( 'options-general.php?page=ai-search&woocommerce_fields_saved=true&tab=woocommerce' ) ); exit; } /** * Clear embedding transients from WordPress cache. */ private function clear_embedding_transients() { global $wpdb; $result = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", '_transient_ai_search_embedding_%', '_transient_timeout_ai_search_embedding_%' ) ); $result += $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", '_transient_ai_service_embedding_%', '_transient_timeout_ai_service_embedding_%' ) ); return $result; } /** * Clear post meta embeddings. */ private function clear_post_meta_embeddings() { global $wpdb; $result = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s", '_ai_search_embedding' ) ); return $result; } /** * Get cache statistics. */ public function get_cache_statistics() { global $wpdb; $transients = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", '_transient_ai_search_embedding_%', '_transient_ai_service_embedding_%' ) ); $post_meta = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = %s", '_ai_search_embedding' ) ); return [ 'transients' => intval( $transients ), 'post_meta' => intval( $post_meta ) ]; } /** * Get all custom fields used by a specific post type. */ public function get_custom_fields_for_post_type( $post_type ) { global $wpdb; $fields = []; // Get regular custom fields (non-underscore prefixed) $query = $wpdb->prepare( " SELECT DISTINCT pm.meta_key FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.post_type = %s AND pm.meta_key NOT LIKE '\_%' AND pm.meta_key != 'ai_search_embedding' AND pm.meta_value != '' ORDER BY pm.meta_key ", $post_type ); $regular_fields = $wpdb->get_col( $query ); if ( $regular_fields ) { $fields = array_merge( $fields, $regular_fields ); } // If ACF is active, get ACF field groups for this post type if ( function_exists( 'acf_get_field_groups' ) ) { $field_groups = acf_get_field_groups( [ 'post_type' => $post_type ] ); foreach ( $field_groups as $field_group ) { $acf_fields = acf_get_fields( $field_group['key'] ); if ( $acf_fields ) { foreach ( $acf_fields as $field ) { // Only include fields that can be converted to text $searchable_types = [ 'text', 'textarea', 'number', 'email', 'url', 'password', 'wysiwyg', 'select', 'checkbox', 'radio', 'button_group', 'true_false', 'post_object', 'relationship', 'taxonomy', 'user', 'date_picker', 'date_time_picker', 'time_picker', 'color_picker' ]; if ( in_array( $field['type'], $searchable_types ) ) { $fields[] = $field['name']; } } } } } // Remove duplicates and sort $fields = array_unique( $fields ); sort( $fields ); return $fields; } /** * Get available WooCommerce fields. */ public function get_woocommerce_fields() { return [ 'content' => [ 'product_description' => 'Product Description', 'product_short_description' => 'Short Description', 'product_sku' => 'Product SKU', ], 'taxonomies' => [ 'product_categories' => 'Product Categories', 'product_tags' => 'Product Tags', ] ]; } /** * Get product attributes. */ public function get_product_attributes() { if ( ! class_exists( 'WooCommerce' ) ) { return []; } return wc_get_attribute_taxonomies(); } /** * Get default WooCommerce fields to include. */ public function get_default_woocommerce_fields() { return [ 'product_description', 'product_short_description', 'product_categories', 'product_tags' ]; } }