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'
];
}
}