supabase_client = AIVectorSearch_Supabase_Client::instance(); $this->api_client = AIVectorSearch_API_Client::instance(); $this->product_sync = AIVectorSearch_Product_Sync::instance(); $this->lite_engine = AIVectorSearch_Lite_Engine::instance(); $this->init_hooks(); } private function init_hooks() { add_action('admin_init', [$this, 'register_settings']); add_action('admin_menu', [$this, 'add_admin_pages']); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']); add_action('admin_notices', [$this, 'show_services_banner']); add_action('admin_notices', [$this, 'show_sql_update_notice']); add_action('admin_init', [$this, 'handle_sql_update_dismiss']); add_action('wp_ajax_aivesese_toggle_help', [$this, 'handle_help_toggle']); add_action('wp_ajax_aivesese_activate_license', [$this, 'handle_license_activation']); add_action('wp_ajax_aivesese_postgres_install_schema', [$this, 'handle_postgres_install_schema']); add_action('wp_ajax_aivesese_postgres_check_status', [$this, 'handle_postgres_check_status']); add_action('wp_ajax_aivesese_update_sold_counts', [$this, 'handle_update_sold_counts']); $this->init_admin_body_classes(); } /** * Enhanced register_settings with PostgreSQL connection string */ public function register_settings() { $settings = [ // Connection mode 'connection_mode' => 'Connection Type', // API mode settings 'license_key' => 'License Key (for API mode)', // Self-hosted mode settings (existing) 'url' => 'Supabase URL (https://xyz.supabase.co)', 'key' => 'Supabase service / anon key', 'store' => 'Store ID (UUID)', 'openai' => 'OpenAI API key (only if semantic search is enabled)', // NEW: PostgreSQL connection string for WP-CLI 'postgres_connection_string' => 'PostgreSQL Connection String (for WP-CLI schema installation)', // Feature toggles 'enable_search' => 'Enable AI search', 'semantic_toggle' => 'Enable semantic (vector) search', 'auto_sync' => 'Auto-sync products on save', 'enable_pdp_similar' => 'PDP "Similar products"', 'enable_cart_below' => 'Below-cart recommendations', 'enable_woodmart_integration' => 'Woodmart live search integration', 'search_results_limit' => 'Search Results Limit', 'lite_index_limit' => 'Lite Mode Index Limit', 'lite_stopwords' => 'Lite Mode Stopwords', 'lite_synonyms' => 'Lite Mode Synonyms', ]; foreach ($settings as $id => $label) { $this->register_setting($id); } add_settings_section('aivesese_section', 'AI Search Configuration', '__return_false', 'aivesese'); $this->add_settings_fields(); } private function register_setting(string $id) { $sanitizers = [ 'connection_mode' => 'sanitize_text_field', 'license_key' => 'aivesese_passthru', 'url' => 'esc_url_raw', 'key' => 'aivesese_passthru', 'store' => 'sanitize_text_field', 'openai' => 'aivesese_passthru', 'postgres_connection_string' => 'aivesese_passthru', // Will be encrypted 'search_results_limit' => [$this, 'sanitize_search_results_limit'], 'lite_index_limit' => 'absint', 'lite_stopwords' => [$this, 'sanitize_lite_stopwords'], 'lite_synonyms' => [$this, 'sanitize_lite_synonyms'], ]; $config = [ 'type' => 'string', 'sanitize_callback' => $sanitizers[$id] ?? 'sanitize_text_field', 'default' => '', ]; // Special handling for connection mode if ($id === 'connection_mode') { $config['default'] = 'lite'; } if ($id === 'search_results_limit') { $config['type'] = 'integer'; $config['default'] = 20; } if ($id === 'lite_index_limit') { $config['type'] = 'integer'; $config['default'] = 500; } // Special handling for checkboxes if (in_array($id, ['enable_search', 'semantic_toggle', 'auto_sync', 'enable_pdp_similar', 'enable_cart_below', 'enable_woodmart_integration'])) { $config['sanitize_callback'] = function($v) { return $v === '1' ? '1' : '0'; }; $config['default'] = $id === 'enable_woodmart_integration' ? '0' : '1'; } register_setting('aivesese_settings', "aivesese_{$id}", $config); } public function sanitize_lite_stopwords($value): string { if (!is_string($value)) { return ''; } return sanitize_textarea_field(wp_unslash($value)); } public function sanitize_lite_synonyms($value): string { if (!is_string($value)) { return ''; } return sanitize_textarea_field(wp_unslash($value)); } public function sanitize_search_results_limit($value): int { $limit = absint($value); if ($limit < 1) { return 20; } if ($limit > 100) { return 100; } return $limit; } private function add_settings_fields() { // Connection mode selector add_settings_field( 'aivesese_connection_mode', 'Connection Type', [$this, 'render_connection_mode_field'], 'aivesese', 'aivesese_section' ); // API mode fields add_settings_field( 'aivesese_license_key', 'License Key', [$this, 'render_license_key_field'], 'aivesese', 'aivesese_section' ); // Self-hosted fields $self_hosted_fields = [ 'url' => 'Supabase URL', 'key' => 'Supabase Service Key', 'store' => 'Store ID (UUID)', 'openai' => 'OpenAI API Key' ]; foreach ($self_hosted_fields as $id => $label) { add_settings_field( "aivesese_{$id}", $label, [$this, 'render_text_field'], 'aivesese', 'aivesese_section', ['field_id' => $id, 'conditional' => 'self_hosted'] ); } // PostgreSQL connection string (NEW) add_settings_field( 'aivesese_postgres_connection_string', 'PostgreSQL Connection String', [$this, 'render_postgres_connection_field'], 'aivesese', 'aivesese_section' ); // Search limit field add_settings_field( 'aivesese_search_results_limit', 'Search Results Limit', [$this, 'render_search_limit_field'], 'aivesese', 'aivesese_section' ); // Feature toggles $checkbox_fields = [ 'enable_search' => 'Enable AI search - Use AI-powered results for store search', 'semantic_toggle' => 'Enable semantic (vector) search - Better relevance', 'auto_sync' => 'Auto-sync products - Automatically sync products when saved/updated', 'enable_pdp_similar' => 'PDP "Similar products" - Show similar products on product pages', 'enable_cart_below' => 'Below-cart recommendations - Show recommendations under cart', 'enable_woodmart_integration' => 'Woodmart live search integration - Enable AI search for Woodmart AJAX search', ]; foreach ($checkbox_fields as $id => $label) { add_settings_field( "aivesese_{$id}", $label, [$this, 'render_checkbox_field'], 'aivesese', 'aivesese_section', ['field_id' => $id, 'label' => $label] ); } } public function render_connection_mode_field() { // Use templated selector instead of inline HTML $current_mode = get_option('aivesese_connection_mode', 'lite'); $api_available = false; // Flip when API service is live $this->load_template('connection-mode-selector-with-lite', compact('current_mode', 'api_available')); return; } public function render_license_key_field() { // Use templated license activation instead of inline HTML $license_key = get_option('aivesese_license_key'); $is_activated = !empty($license_key) && get_option('aivesese_api_activated') === '1'; $activation_data = []; if ($is_activated && method_exists($this->api_client, 'get_status')) { $status = $this->api_client->get_status(); if (is_array($status)) { $activation_data = $status; } } $this->load_template('license-activation', compact('license_key', 'is_activated', 'activation_data')); return; } public function render_text_field($args) { $field_id = $args['field_id']; $value = get_option("aivesese_{$field_id}"); printf( '', esc_attr($field_id), esc_attr($field_id), esc_attr($value) ); if ($field_id === 'openai') { echo '

Only required if semantic search is enabled

'; } } public function render_search_limit_field() { $value = aivesese_get_search_results_limit(); printf( '', esc_attr($value) ); echo '

Maximum number of products to return in search results (1-100). Default: 20

'; } public function render_checkbox_field($args) { $field_id = $args['field_id']; $label = $args['label']; $value = get_option("aivesese_{$field_id}"); printf( '', esc_attr($field_id), checked($value, '1', false), esc_html($label) ); } /** * Handle license activation AJAX */ public function handle_license_activation() { check_ajax_referer('aivesese_license_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized']); return; } $license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? '')); if (empty($license_key)) { wp_send_json_error(['message' => 'License key is required']); return; } // Test the license key with the API $result = $this->api_client->activate_license($license_key); if ($result['success']) { // Save license and switch to API mode update_option('aivesese_license_key', $license_key); update_option('aivesese_connection_mode', 'api'); update_option('aivesese_api_activated', '1'); update_option('aivesese_store', $result['store_id']); wp_send_json_success([ 'message' => 'License activated successfully!', 'store_id' => $result['store_id'], 'plan' => $result['plan'] ]); } else { wp_send_json_error(['message' => $result['message']]); } } /** * Enhanced settings page with custom field rendering */ public function render_settings_page() { $connection_mode = get_option('aivesese_connection_mode', 'lite'); echo '
'; echo '

' . esc_html__('AI Vector Search Settings', 'ai-vector-search-semantic') . '

'; // Show different descriptions based on mode if ($connection_mode === 'api') { echo '

' . esc_html__('You are using our managed API service. No additional setup required!', 'ai-vector-search-semantic') . '

'; } elseif ($connection_mode === 'lite') { echo '

' . esc_html__('Lite mode runs locally. Configure the search engine options below.', 'ai-vector-search-semantic') . '

'; } else { echo '

' . esc_html__('Configure your own Supabase project and optionally enable semantic search using OpenAI.', 'ai-vector-search-semantic') . '

'; } // Show help section only for self-hosted mode if ($connection_mode === 'self_hosted') { $this->render_help_section(); $this->render_sold_count_section(); } echo '
'; settings_fields('aivesese_settings'); do_settings_sections('aivesese'); if ($connection_mode === 'lite') { echo '
'; $this->load_template('lite-mode-config'); echo '
'; } submit_button(); echo '
'; echo '
'; } /** * Updated enqueue_admin_assets method - Now properly organized */ public function enqueue_admin_assets($hook) { $page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; if (!in_array($page, ['aivesese', 'aivesese-status', 'aivesese-sync', 'aivesese-analytics'], true)) { return; } // Enqueue styles first $this->enqueue_admin_styles(); // Then enqueue scripts (which depend on styles being loaded) $this->enqueue_admin_scripts(); } /** * Enhanced status page with API/self-hosted detection */ public function render_status_page() { echo '
'; echo '

' . esc_html__('AI Vector Search Status', 'ai-vector-search-semantic') . '

'; $connection_mode = get_option('aivesese_connection_mode', 'lite'); if ($connection_mode === 'api') { $this->render_api_status(); } elseif ($connection_mode === 'lite') { $this->render_lite_status(); } else { $this->render_self_hosted_status(); } // Add status page footer action hook do_action('aivesese_status_page_footer'); echo '
'; } private function render_api_status() { $license_key = get_option('aivesese_license_key'); if (empty($license_key)) { echo '

No license key configured. Please go to Settings to activate your license.

'; return; } // Get status from API $status = $this->api_client->get_status(); if (!$status) { echo '

Unable to connect to API service. Please check your license key.

'; return; } echo '

✅ Connected to ZZZ Solutions API Service

'; // Show subscription info echo '

Subscription Status

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; if (!empty($status['expires_at'])) { echo ''; } echo '
Plan' . esc_html(ucfirst($status['plan'])) . '
Status' . esc_html(ucfirst($status['status'])) . '
Products Synced' . number_format($status['products_count']) . '
Searches This Month' . number_format($status['usage']['searches_this_month']) . '
API Calls Today' . number_format($status['usage']['api_calls_today']) . '
Next Payment' . esc_html(gmdate('M j, Y', strtotime($status['expires_at']))) . '
'; // Show usage limits if (!empty($status['limits'])) { echo '

Usage Limits

'; echo '
'; $this->render_usage_bar( 'Products', $status['products_count'], $status['limits']['products_limit'], 'products' ); $this->render_usage_bar( 'Monthly Searches', $status['usage']['searches_this_month'], $status['limits']['searches_limit'], 'searches' ); echo '
'; } } private function render_usage_bar($label, $current, $limit, $type) { $percentage = $limit > 0 ? min(($current / $limit) * 100, 100) : 0; $bar_class = $percentage > 90 ? 'usage-critical' : ($percentage > 70 ? 'usage-warning' : 'usage-good'); echo '
'; echo '
'; echo '' . esc_html($label) . ''; echo '' . number_format($current) . ($limit > 0 ? ' / ' . number_format($limit) : '') . ''; echo '
'; echo '
'; echo '
'; echo '
'; echo '
'; } private function render_self_hosted_status() { if (!$this->is_configured()) { $this->render_configuration_error(); return; } $health = $this->supabase_client->get_store_health(); if (empty($health)) { $this->render_connection_error(); return; } $this->render_health_overview($health[0]); $this->render_configuration_summary(); $this->render_quick_actions(); } private function render_lite_status() { $stats = $this->lite_engine ? $this->lite_engine->get_index_stats() : ['indexed_products' => 0, 'total_terms' => 0, 'last_built' => 0]; $limit_option = get_option('aivesese_lite_index_limit', '500'); $limit_value = is_numeric($limit_option) ? (int) $limit_option : 0; if ($limit_value <= 0) { $limit_label = esc_html__('All products', 'ai-vector-search-semantic'); } else { $limit_label = sprintf( esc_html__('Latest %s products', 'ai-vector-search-semantic'), number_format_i18n($limit_value) ); } $last_built = !empty($stats['last_built']) ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), (int) $stats['last_built']) : esc_html__('Not built yet', 'ai-vector-search-semantic'); echo '

' . esc_html__('Lite mode is active. Your search index runs locally without Supabase or OpenAI configuration.', 'ai-vector-search-semantic') . '

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
' . esc_html__('Products Indexed', 'ai-vector-search-semantic') . '' . number_format_i18n((int) ($stats['indexed_products'] ?? 0)) . '
' . esc_html__('Unique Terms', 'ai-vector-search-semantic') . '' . number_format_i18n((int) ($stats['total_terms'] ?? 0)) . '
' . esc_html__('Index Limit', 'ai-vector-search-semantic') . '' . esc_html($limit_label) . '
' . esc_html__('Last Rebuild', 'ai-vector-search-semantic') . '' . esc_html($last_built) . '
'; echo '

' . esc_html__('Manage Lite settings', 'ai-vector-search-semantic') . '

'; } public function add_admin_pages() { // Add top-level menu page add_menu_page( 'AI Vector Search', // Page title 'AI Vector Search', // Menu title 'manage_options', // Capability 'aivesese', // Menu slug [$this, 'render_settings_page'], // Callback function 'dashicons-search', // Icon 30 // Position ); // Add submenu pages under the main menu add_submenu_page( 'aivesese', // Parent slug 'AI Vector Search Settings', // Page title 'Settings', // Menu title 'manage_options', // Capability 'aivesese', // Menu slug (same as parent for first submenu) [$this, 'render_settings_page'] // Callback function ); add_submenu_page( 'aivesese', 'AI Vector Search Status', 'Status', 'manage_options', 'aivesese-status', [$this, 'render_status_page'] ); add_submenu_page( 'aivesese', 'Sync Products', 'Sync Products', 'manage_options', 'aivesese-sync', [$this, 'render_sync_page'] ); // Add Analytics page if analytics is available if (class_exists('AIVectorSearch_Analytics')) { add_submenu_page( 'aivesese', 'Search Analytics', 'Analytics', 'manage_options', 'aivesese-analytics', [AIVectorSearch_Analytics::instance(), 'render_analytics_page_template'] ); } } public function render_sync_page() { echo '
'; echo '

' . esc_html__('Sync Products to Supabase', 'ai-vector-search-semantic') . '

'; $connection_mode = get_option('aivesese_connection_mode', 'lite'); if ($connection_mode === 'lite') { echo '

' . esc_html__('Lite mode manages its search index automatically. No Supabase sync is required.', 'ai-vector-search-semantic') . '

'; echo '

' . esc_html__('Manage Lite settings', 'ai-vector-search-semantic') . '

'; echo '
'; return; } if (!$this->is_configured()) { $this->render_configuration_error(); echo ''; return; } $this->handle_sync_actions(); $this->render_sync_overview(); $this->render_sync_actions(); echo ''; } private function is_configured(): bool { $mode = get_option('aivesese_connection_mode', 'lite'); if ($mode === 'lite') { return true; } return get_option('aivesese_store') && get_option('aivesese_url') && get_option('aivesese_key'); } private function render_configuration_error() { echo '

'; echo esc_html__('Configuration incomplete! Please configure your Supabase settings first.', 'ai-vector-search-semantic'); echo ' '; echo esc_html__('Go to Settings', 'ai-vector-search-semantic') . ''; echo '

'; } private function render_connection_error() { echo '

'; echo esc_html__('Unable to connect to Supabase or no data found. Check your configuration and ensure the SQL has been installed.', 'ai-vector-search-semantic'); echo '

'; } private function render_health_overview(array $data) { echo '

✅ Successfully connected to Supabase!

'; echo '

Store Health Overview

'; echo ''; echo ''; echo ''; $total = intval($data['total_products']); $published = intval($data['published_products']); $in_stock = intval($data['in_stock_products']); $with_embeddings = intval($data['with_embeddings']); $this->render_health_row('Total Products', $total, $total > 0); $this->render_health_row('Published Products', $published, $published > 0); $this->render_health_row('In Stock Products', $in_stock, $in_stock > 0); $this->render_embeddings_status_row($with_embeddings, $published); echo '
MetricCountStatus
'; } private function render_health_row(string $label, int $count, bool $is_good) { echo ''; echo '' . esc_html($label) . ''; echo '' . number_format($count) . ''; echo '' . ($is_good ? '✅' : '⚠️') . ''; echo ''; } private function render_embeddings_status_row(int $with_embeddings, int $published) { echo ''; echo 'With Embeddings'; echo '' . number_format($with_embeddings) . ''; echo ''; if ($with_embeddings == 0) { echo '❌ No embeddings found'; } elseif ($with_embeddings == $published) { echo '✅ All products have embeddings'; } else { $percent = round(($with_embeddings / $published) * 100, 1); echo '⚠️ ' . esc_html($percent) . '% coverage'; } echo ''; } private function render_configuration_summary() { echo '

' . esc_html__('Configuration Summary', 'ai-vector-search-semantic') . '

'; echo ''; echo ''; $config_items = [ 'Store ID' => get_option('aivesese_store'), 'Supabase URL' => get_option('aivesese_url'), 'AI Search' => get_option('aivesese_enable_search', '1') === '1' ? '✅ Enabled' : '❌ Disabled', 'Semantic Search' => get_option('aivesese_semantic_toggle') === '1' ? '✅ Enabled' : '❌ Disabled', 'OpenAI Key' => get_option('aivesese_openai') ? '✅ Configured' : '❌ Not set', 'Woodmart Integration' => $this->get_woodmart_status(), ]; foreach ($config_items as $label => $value) { echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html($label) . ''; if (in_array($label, ['Store ID', 'Supabase URL'])) { echo '' . esc_html($value) . ''; } else { echo wp_kses_post($value); } echo '
'; } private function get_woodmart_status(): string { $is_enabled = get_option('aivesese_enable_woodmart_integration', '0') === '1'; $is_woodmart_active = defined('WOODMART_THEME_DIR') || wp_get_theme()->get('Name') === 'Woodmart'; if (!$is_woodmart_active) { return '⚪ Woodmart not detected'; } if ($is_enabled) { return '✅ Enabled (Woodmart detected)'; } return '❌ Disabled (Woodmart available)'; } private function render_quick_actions() { echo '

' . esc_html__('Quick Actions', 'ai-vector-search-semantic') . '

'; echo '

'; echo '' . esc_html__('Configure Settings', 'ai-vector-search-semantic') . ' '; echo '' . esc_html__('Refresh Status', 'ai-vector-search-semantic') . ''; echo '

'; } private function handle_sync_actions() { if (!isset($_POST['action']) || !check_admin_referer('aivesese_sync')) { return; } $action = sanitize_key(wp_unslash($_POST['action'])); switch ($action) { case 'sync_all': $this->handle_sync_all(); break; case 'sync_batch': $this->handle_sync_batch(); break; case 'generate_embeddings': $this->handle_generate_embeddings(); break; } } private function handle_sync_all() { $result = $this->product_sync->sync_all_products(); if ($result['success']) { echo '

Successfully synced ' . esc_attr($result['synced']) . '/' . esc_attr($result['total']) . ' products to Supabase!

'; } else { echo '

Sync failed: ' . esc_html($result['message']) . '

'; } } private function handle_sync_batch() { $batch_size = isset($_POST['batch_size']) ? absint(wp_unslash($_POST['batch_size'])) : 50; $offset = isset($_POST['offset']) ? absint(wp_unslash($_POST['offset'])) : 0; $result = $this->product_sync->sync_products_batch($batch_size, $offset); if ($result['success']) { echo '

Successfully synced batch: ' . esc_attr($result['synced']) . '/' . esc_attr($result['total']) . ' products (offset: ' . esc_attr($offset) . ')

'; $this->render_next_batch_form($batch_size, $offset + $batch_size); } else { echo '

Batch sync failed: ' . esc_html($result['message']) . '

'; } } private function handle_generate_embeddings() { $result = $this->product_sync->generate_missing_embeddings(); if ($result['success']) { if ($result['updated'] === 0) { echo '

No products without embeddings found in Supabase.

'; } else { echo '

Generated embeddings for ' . esc_html($result['updated']) . ' products. Skipped ' . esc_html($result['skipped']) . '.

'; } } else { echo '

' . esc_html($result['message']) . '

'; } } private function render_next_batch_form(int $batch_size, int $next_offset) { echo '
'; echo '

Continue with next batch:

'; echo '
'; wp_nonce_field('aivesese_sync'); echo ''; echo ''; echo ''; echo ''; echo '
'; echo '
'; } private function render_sync_overview() { $total_products = wp_count_posts('product')->publish; $synced_count = $this->supabase_client->get_synced_count(); echo '

Sync Overview

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
WooCommerce Products' . number_format($total_products) . '
Synced to Supabase' . number_format($synced_count) . '
Sync Status'; if ($synced_count == 0) { echo '❌ No products synced'; } elseif ($synced_count >= $total_products) { echo '✅ All products synced'; } else { $percent = round(($synced_count / $total_products) * 100, 1); echo '⚠️ ' . esc_html($percent) . '% synced (' . absint($synced_count) . '/' . absint($total_products) . ')'; } echo '
'; } private function render_sync_actions() { echo '

Sync Actions

'; // Full sync echo '
'; echo '

🔄 Full Sync

'; echo '

Sync all WooCommerce products to Supabase. This may take a while for large catalogs.

'; echo '
'; wp_nonce_field('aivesese_sync'); echo ''; echo ''; echo '
'; echo '
'; // Batch sync echo '
'; echo '

⚡ Batch Sync

'; echo '

Sync products in smaller batches to avoid timeouts.

'; echo '
'; wp_nonce_field('aivesese_sync'); echo ''; echo '
'; echo ' '; echo ' '; echo ''; echo '
'; echo '
'; echo '
'; // Embeddings generation if (get_option('aivesese_semantic_toggle') === '1' && get_option('aivesese_openai')) { echo '
'; echo '

🧠 Generate Embeddings

'; echo '

Generate or update OpenAI embeddings for products that don\'t have them.

'; echo '
'; wp_nonce_field('aivesese_sync'); echo ''; echo ''; echo '
'; echo '
'; } echo '
'; } private function render_help_section() { $user_id = get_current_user_id(); $is_open = get_user_meta($user_id, '_aivesese_help_open', true); $is_open = ($is_open === '' ? '1' : $is_open); $open_attr = ($is_open === '1') ? ' open' : ''; echo '
'; echo '
'; echo 'Setup Guide click to expand/collapse'; $this->render_setup_instructions(); $this->render_sql_section(); echo '
'; } private function render_setup_instructions() { $template = AIVESESE_PLUGIN_PATH . 'assets/templates/setup-instructions.php'; if (! file_exists($template)) { return; } include $template; } private function render_sql_section() { $connection_mode = get_option('aivesese_connection_mode', 'lite'); if ($connection_mode !== 'self_hosted') { return; // Don't show SQL section for API mode } echo '
'; echo '
'; if (!$this->validate_supabase_connection()) { $this->render_connection_required_notice(); echo '
'; return; } // Load migration runner for status require_once AIVESESE_PLUGIN_PATH . 'includes/migrations/class-runner.php'; $migration_status = \ZZZSolutions\VectorSearch\Migrations\Runner::getStatus(); $this->render_installation_options($migration_status); echo ''; } private function get_sql_content(): string { $base = plugin_dir_path(__FILE__); $candidates = [ $base . '../assets/sql/supabase.sql', $base . '../admin/sql/supabase.sql', $base . '../supabase.sql', ]; foreach ($candidates as $path) { if (file_exists($path)) { $content = file_get_contents($path); // Add version header if not present if (strpos($content, '-- AI Vector Search SQL v2.0') === false) { $version_header = "-- AI Vector Search SQL v2.0 - Updated with SKU search and enhanced FTS\n" . "-- Run this entire script in Supabase SQL Editor\n" . "-- New features: Partial SKU search, Better ranking, Woodmart integration\n\n"; $content = $version_header . $content; } return $content; } } return ''; } private function enqueue_help_script() { wp_register_script('aivesese-help', false, [], AIVESESE_PLUGIN_VERSION, true); wp_enqueue_script('aivesese-help'); $data = [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aivesese_help_nonce'), ]; wp_add_inline_script('aivesese-help', 'window.AISupabaseHelp=' . wp_json_encode($data) . ';', 'before'); $help_script = $this->get_help_toggle_script(); wp_add_inline_script('aivesese-help', $help_script); } private function enqueue_admin_styles() { $current_page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; // Main admin interface styles (always load on plugin pages) wp_enqueue_style( 'aivesese-admin-interface', AIVESESE_PLUGIN_URL . 'assets/css/admin-interface.css', [], AIVESESE_PLUGIN_VERSION ); // PostgreSQL installation styles (only on settings page) if ($current_page === 'aivesese') { wp_enqueue_style( 'aivesese-postgres-install', AIVESESE_PLUGIN_URL . 'assets/css/postgres-installation.css', ['aivesese-admin-interface'], AIVESESE_PLUGIN_VERSION ); } // Analytics dashboard styles (only on analytics page) if ($current_page === 'aivesese-analytics') { wp_enqueue_style( 'aivesese-analytics-dashboard', AIVESESE_PLUGIN_URL . 'assets/css/analytics-dashboard.css', ['aivesese-admin-interface'], AIVESESE_PLUGIN_VERSION ); } // Add any conditional inline styles if needed $this->add_conditional_styles($current_page); } /** * Add conditional inline styles (minimal, only when necessary) */ private function add_conditional_styles($current_page) { $connection_mode = get_option('aivesese_connection_mode', 'lite'); // Add body class-based styles for connection mode $inline_css = " body.aivesese-mode-{$connection_mode} .{$connection_mode}-only { display: block !important; } body.aivesese-mode-{$connection_mode} .hide-in-{$connection_mode} { display: none !important; } "; // Only add if we have conditional styles to add if (!empty($inline_css)) { wp_add_inline_style('aivesese-admin-interface', $inline_css); } } private function enqueue_admin_scripts() { $current_page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; // Main admin interface script (always load on plugin pages) wp_enqueue_script( 'aivesese-admin-interface', AIVESESE_PLUGIN_URL . 'assets/js/admin-interface.js', ['jquery'], AIVESESE_PLUGIN_VERSION, true ); // Localize script with necessary data wp_localize_script('aivesese-admin-interface', 'aivesese_admin', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aivesese_admin_nonce'), 'license_nonce' => wp_create_nonce('aivesese_license_nonce'), 'help_nonce' => wp_create_nonce('aivesese_help_nonce'), 'analytics_nonce' => wp_create_nonce('aivs_analytics_nonce'), 'strings' => [ 'activating' => __('Activating...', 'ai-vector-search-semantic'), 'processing' => __('Processing...', 'ai-vector-search-semantic'), 'license_copied' => __('License key copied to clipboard!', 'ai-vector-search-semantic'), 'sql_copied' => __('SQL copied to clipboard.', 'ai-vector-search-semantic'), ] ]); // PostgreSQL installation script (only on settings page) if ($current_page === 'aivesese') { wp_enqueue_script( 'aivesese-postgres-install', AIVESESE_PLUGIN_URL . 'assets/js/postgres-installation.js', ['jquery', 'aivesese-admin-interface'], AIVESESE_PLUGIN_VERSION, true ); wp_localize_script('aivesese-postgres-install', 'aivesese_postgres', [ 'install_nonce' => wp_create_nonce('aivesese_postgres_install_nonce'), 'status_nonce' => wp_create_nonce('aivesese_postgres_status_nonce'), 'admin_url' => admin_url(), ]); } // Analytics dashboard script (only on analytics page) if ($current_page === 'aivesese-analytics') { wp_enqueue_script( 'aivesese-analytics-dashboard', AIVESESE_PLUGIN_URL . 'assets/js/analytics-dashboard.js', ['jquery'], AIVESESE_PLUGIN_VERSION, true ); wp_localize_script('aivesese-analytics-dashboard', 'aivesese_analytics', [ 'preview_nonce' => wp_create_nonce('aivs_preview_nonce'), 'stats_nonce' => wp_create_nonce('aivs_stats_nonce'), 'tracking_nonce' => wp_create_nonce('aivs_tracking_nonce'), 'analytics_nonce' => wp_create_nonce('aivs_analytics_nonce'), 'export_nonce' => wp_create_nonce('aivesese_export_analytics'), ]); } // Woodmart integration (if enabled) if (get_option('aivesese_enable_woodmart_integration', '0') === '1') { wp_enqueue_script( 'aivesese-woodmart-integration', AIVESESE_PLUGIN_URL . 'assets/js/woodmart-integration.js', ['jquery'], AIVESESE_PLUGIN_VERSION, true ); wp_localize_script('aivesese-woodmart-integration', 'aivesese_woodmart', [ 'ajax_url' => admin_url('admin-ajax.php'), 'search_nonce' => wp_create_nonce('aivesese_search_nonce'), 'tracking_nonce' => wp_create_nonce('aivs_tracking_nonce'), 'enabled' => '1', ]); } } public function show_services_banner() { $screen = function_exists('get_current_screen') ? get_current_screen() : null; $allowed_screens = [ 'toplevel_page_aivesese', 'aivesese_page_aivesese', 'aivesese_page_aivesese-status', 'aivesese_page_aivesese-sync', 'aivesese_page_aivesese-analytics' ]; if (!$screen || !in_array($screen->id, $allowed_screens, true)) { return; } echo '
'; echo '
🚀
'; echo '
'; echo 'Need a hand with AI search or Supabase?
'; echo 'Our team at ZZZ Solutions can install, customise and tune everything for you.'; echo '
'; echo 'See Services'; echo '
'; } public function handle_help_toggle() { check_ajax_referer('aivesese_help_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'forbidden'], 403); } $open = (isset($_POST['open']) && wp_unslash($_POST['open']) === '1') ? '1' : '0'; update_user_meta(get_current_user_id(), '_aivesese_help_open', $open); wp_send_json_success(['open' => $open]); } public function show_sql_update_notice() { if (!current_user_can('manage_options') || get_option('aivesese_sql_v2_dismissed')) { return; } // Only show on relevant admin pages $screen = function_exists('get_current_screen') ? get_current_screen() : null; $allowed_screens = [ 'toplevel_page_aivesese', 'aivesese_page_aivesese', 'aivesese_page_aivesese-status', 'aivesese_page_aivesese-sync', 'aivesese_page_aivesese-analytics', 'plugins' ]; if (!$screen || !in_array($screen->id, $allowed_screens, true)) { return; } echo '
'; echo '

🔄 AI Vector Search - SQL Update Required

'; echo '

New features added! We\'ve enhanced the search with:

'; echo ''; echo '

Action required: Please update your Supabase SQL to get these features:

'; echo '
    '; echo '
  1. Go to Settings - AI Supabase
  2. '; echo '
  3. Expand the "Setup Guide" section
  4. '; echo '
  5. Copy the updated SQL and run it in Supabase - SQL Editor
  6. '; echo '
  7. The new functions will be added/updated automatically
  8. '; echo '
'; echo '

'; echo 'Update SQL Now '; echo 'I\'ve Updated It'; echo '

'; echo '
'; } public function handle_sql_update_dismiss() { if (isset($_GET['aivesese_sql_v2_dismiss']) && check_admin_referer('aivesese_sql_v2_nonce')) { update_option('aivesese_sql_v2_dismissed', time()); wp_safe_redirect(remove_query_arg(['aivesese_sql_v2_dismiss', '_wpnonce'])); exit; } } /** * Render PostgreSQL connection string field */ public function render_postgres_connection_field() { $connection_mode = get_option('aivesese_connection_mode', 'lite'); $value = get_option('aivesese_postgres_connection_string'); $has_value = !empty($value); if ($connection_mode !== 'self_hosted') { echo '

PostgreSQL connection is only needed for self-hosted mode.

'; return; } // Use template file instead of inline HTML $template_vars = compact('connection_mode', 'value', 'has_value'); $this->load_template('postgres-connection', $template_vars); } /** * Render sold count update section (self-hosted only). */ private function render_sold_count_section() { $ranges = [ '7' => 'Last 7 days', '30' => 'Last 30 days', '90' => 'Last 90 days', ]; $template_vars = compact('ranges'); $this->load_template('sold-count-update', $template_vars); } /** * Render PostgreSQL help section (extracted from inline HTML) */ private function render_postgres_help_section() { include AIVESESE_PLUGIN_PATH . 'assets/templates/postgres-help-section.php'; } /** * Handle PostgreSQL schema installation via AJAX */ public function handle_postgres_install_schema() { check_ajax_referer('aivesese_postgres_install_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized access']); return; } // Load the migration runner require_once AIVESESE_PLUGIN_PATH . 'includes/migrations/class-runner.php'; // Run the migration $result = \ZZZSolutions\VectorSearch\Migrations\Runner::run(); if ($result['ok']) { wp_send_json_success([ 'message' => $result['msg'], 'details' => $result['details'] ?? [] ]); } else { wp_send_json_error([ 'message' => $result['msg'], 'details' => $result['details'] ?? [] ]); } } /** * Handle PostgreSQL status check via AJAX */ public function handle_postgres_check_status() { check_ajax_referer('aivesese_postgres_status_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized access']); return; } // Load the migration runner require_once AIVESESE_PLUGIN_PATH . 'includes/migrations/class-runner.php'; $status = \ZZZSolutions\VectorSearch\Migrations\Runner::getStatus(); wp_send_json_success($status); } /** * Handle sold count update via AJAX. */ public function handle_update_sold_counts() { check_ajax_referer('aivesese_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized access']); return; } $days = isset($_POST['days']) ? absint($_POST['days']) : 30; if ($days < 1) { $days = 30; } $result = $this->product_sync->update_sold_counts($days); if (!empty($result['success'])) { wp_send_json_success($result); } wp_send_json_error($result); } /** * Render installation options (PostgreSQL + Manual) */ private function render_installation_options(array $migration_status) { echo '

🗄️ Database Schema Installation

'; // Show current status if already installed $installed_time = get_option('aivesese_schema_installed'); $install_method = get_option('aivesese_schema_install_method', 'unknown'); if ($installed_time) { echo '
'; echo '

✅ Schema Already Installed

'; echo '

Installed on ' . gmdate('M j, Y \a\t g:i A', $installed_time) . ''; if ($install_method) { echo ' via ' . esc_html($install_method) . ''; } echo '

'; echo '
'; echo ' '; echo ''; echo '
'; echo '
'; } echo '
'; // PostgreSQL installation option if ($migration_status['can_run']) { $this->render_postgres_installation_option(); } else { $this->render_postgres_installation_unavailable($migration_status); } // Manual installation option $this->render_manual_installation_option(); echo '
'; } /** * Render PostgreSQL installation option (available) */ private function render_postgres_installation_option() { include AIVESESE_PLUGIN_PATH . 'assets/templates/postgres-installation-option.php'; } /** * Render PostgreSQL installation unavailable notice */ private function render_postgres_installation_unavailable(array $status) { include AIVESESE_PLUGIN_PATH . 'assets/templates/postgres-installation-unavailable.php'; } /** * Render manual installation option */ private function render_manual_installation_option() { $sql_content = $this->get_sql_content(); include AIVESESE_PLUGIN_PATH . 'assets/templates/manual-installation-option.php'; } /** * Render manual installation steps */ private function render_manual_installation_steps() { $sql_content = $this->get_sql_content(); // Use template file instead of inline HTML $template_vars = compact('sql_content'); $this->load_template('manual-installation', $template_vars); } /** * Validate Supabase connection configuration */ private function validate_supabase_connection(): bool { $url = trim(get_option('aivesese_url', '')); $key = trim(get_option('aivesese_key', '')); $store_id = trim(get_option('aivesese_store', '')); return !empty($url) && !empty($key) && !empty($store_id); } /** * Render notice when Supabase connection is not configured */ private function render_connection_required_notice() { echo '
'; echo '

⚙️ Configuration Required

'; echo '

Please configure your Supabase connection settings above to use the schema installation features.

'; echo '

You need to provide:

'; echo ''; echo '

Scroll up and fill in the required fields, then click "Save Changes".

'; echo '
'; } /** * Add body classes for better CSS targeting */ public function add_admin_body_class($classes) { $screen = get_current_screen(); if ($screen && strpos($screen->id, 'aivesese') !== false) { $connection_mode = get_option('aivesese_connection_mode', 'lite'); $classes .= ' aivesese-admin aivesese-mode-' . $connection_mode; } return $classes; } /** * Register the admin body class filter */ private function init_admin_body_classes() { add_filter('admin_body_class', [$this, 'add_admin_body_class']); } /** * Load template file helper method (NEW) */ private function load_template($template_name, $vars = []) { $template_name = basename((string) $template_name); $template_path = AIVESESE_PLUGIN_PATH . "assets/templates/{$template_name}.php"; if (file_exists($template_path)) { // Extract variables for template extract($vars); include $template_path; } else { error_log("AI Vector Search: Template not found - {$template_path}"); echo "

Template missing: {$template_name}.php

"; } } /** * Enhanced template loading with error handling (NEW) */ private function load_template_with_fallback($template_name, $vars = [], $fallback_content = '') { $template_name = basename((string) $template_name); $template_path = AIVESESE_PLUGIN_PATH . "assets/templates/{$template_name}.php"; if (file_exists($template_path)) { extract($vars); ob_start(); include $template_path; return ob_get_clean(); } // Log error and return fallback error_log("AI Vector Search: Template not found - {$template_path}"); return $fallback_content; } }