load_dependencies(); $this->init_components(); $this->register_hooks(); } /** * Load required files */ private function load_dependencies() { require_once plugin_dir_path( __FILE__ ) . 'class-settings-pages.php'; require_once plugin_dir_path( __FILE__ ) . 'class-setup-wizard.php'; require_once plugin_dir_path( __FILE__ ) . 'class-post-meta-box.php'; require_once plugin_dir_path( __FILE__ ) . 'class-deactivation-feedback.php'; } /** * Initialize components */ private function init_components() { $this->settings_pages = new AI_Search_Settings_Pages(); $this->setup_wizard = new AI_Search_Setup_Wizard(); $this->post_meta_box = new AI_Search_Post_Meta_Box(); new AI_Search_Deactivation_Feedback(); } /** * Register admin hooks */ private function register_hooks() { add_action( 'admin_menu', [ $this, 'register_settings_menu' ] ); add_action( 'admin_notices', [ $this->setup_wizard, 'show_setup_notice' ] ); add_action( 'admin_notices', [ $this->setup_wizard, 'show_setup_success_notice' ] ); add_action( 'admin_notices', [ $this, 'show_domain_mismatch_notice' ] ); add_action( 'admin_notices', [ $this, 'show_woocommerce_welcome_notice' ] ); add_action( 'admin_notices', [ $this, 'show_quota_warning_banner' ] ); add_action( 'admin_notices', [ $this, 'show_pro_upgrade_banner' ] ); add_action( 'admin_post_ai_search_snooze_quota_banner', [ $this, 'snooze_quota_banner' ] ); add_action( 'admin_post_ai_search_dismiss_quota_banner_forever', [ $this, 'dismiss_quota_banner_forever' ] ); add_action( 'admin_post_ai_search_dismiss_pro_banner_forever', [ $this, 'dismiss_pro_banner_forever' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); add_action( 'admin_enqueue_scripts', [ $this->setup_wizard, 'enqueue_scripts' ] ); add_action( 'admin_head', [ $this, 'add_menu_icon_styles' ] ); add_filter( 'parent_file', [ $this, 'fix_admin_parent_file' ] ); add_filter( 'submenu_file', [ $this, 'fix_admin_submenu_file' ] ); // Admin post actions add_action( 'admin_post_ai_search_generate_embeddings', [ $this->settings_pages, 'generate_selected_embeddings' ] ); add_action( 'admin_post_ai_search_generate_term_embeddings', [ $this->settings_pages, 'generate_term_embeddings_bulk' ] ); add_action( 'admin_post_ai_search_clear_cache', [ $this->settings_pages, 'clear_embeddings_cache' ] ); add_action( 'admin_post_ai_search_clear_cache_filtered', [ $this->settings_pages, 'clear_embeddings_cache_filtered' ] ); add_action( 'admin_post_ai_search_validate_token', [ $this->settings_pages, 'validate_service_token' ] ); add_action( 'admin_post_ai_search_recover_token', [ $this->settings_pages, 'recover_service_token' ] ); add_action( 'admin_post_ai_search_save_custom_fields', [ $this->settings_pages, 'save_custom_fields_settings' ] ); add_action( 'admin_post_ai_search_save_woocommerce_fields', [ $this->settings_pages, 'save_woocommerce_fields_settings' ] ); add_action( 'admin_post_ai_search_refresh_quota', [ $this->settings_pages, 'refresh_quota_data' ] ); add_action( 'admin_post_ai_search_claim_subscription', [ $this->settings_pages, 'handle_claim_subscription' ] ); add_action( 'admin_post_ai_search_release_slot', [ $this->settings_pages, 'handle_release_slot' ] ); add_action( 'admin_post_ai_search_dismiss_reindex_notice', [ $this->settings_pages, 'handle_dismiss_reindex_notice' ] ); add_action( 'admin_post_ai_search_complete_setup', [ $this->setup_wizard, 'complete_setup' ] ); add_action( 'admin_post_ai_search_reconnect_service', [ $this, 'reconnect_service' ] ); // AJAX actions add_action( 'wp_ajax_ai_search_validate_token', [ $this->settings_pages, 'validate_service_token' ] ); add_action( 'wp_ajax_ai_search_get_taxonomies', [ $this, 'ajax_get_taxonomies' ] ); add_action( 'wp_ajax_ai_search_get_terms', [ $this, 'ajax_get_terms' ] ); add_action( 'wp_ajax_ai_search_dismiss_woo_welcome', [ $this, 'ajax_dismiss_woo_welcome' ] ); add_action( 'admin_post_ai_search_dismiss_blocks_notice', [ $this, 'dismiss_blocks_notice' ] ); add_action( 'admin_post_ai_search_snooze_pro_banner', [ $this, 'snooze_pro_banner' ] ); add_action( 'wp_ajax_ai_search_wizard_generate_batch', [ $this->setup_wizard, 'ajax_generate_batch' ] ); add_action( 'wp_ajax_ai_search_wizard_prepare_sync', [ $this->setup_wizard, 'ajax_prepare_sync' ] ); add_action( 'wp_ajax_ai_search_dismiss_wizard', [ $this->setup_wizard, 'ajax_dismiss_wizard' ] ); add_action( 'wp_ajax_ai_search_dismiss_setup_success', [ $this->setup_wizard, 'ajax_dismiss_setup_success' ] ); add_action( 'wp_ajax_ai_search_test_query', [ $this->settings_pages, 'ajax_test_query' ] ); } /** * AJAX handler to get taxonomies for a post type. */ public function ajax_get_taxonomies() { check_ajax_referer( 'ai_search_ajax' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Unauthorized' ); } $post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : 'post'; $taxonomies = get_object_taxonomies( $post_type, 'objects' ); $result = []; foreach ( $taxonomies as $taxonomy ) { if ( $taxonomy->public && $taxonomy->show_ui ) { $result[ $taxonomy->name ] = [ 'label' => $taxonomy->label, 'name' => $taxonomy->name, ]; } } wp_send_json_success( $result ); } /** * AJAX handler to get terms for a taxonomy. */ public function ajax_get_terms() { check_ajax_referer( 'ai_search_ajax' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Unauthorized' ); } $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_text_field( $_POST['taxonomy'] ) : ''; if ( empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) ) { wp_send_json_error( 'Invalid taxonomy' ); } $terms = get_terms( [ 'taxonomy' => $taxonomy, 'hide_empty' => false, 'number' => 100, ] ); if ( is_wp_error( $terms ) ) { wp_send_json_error( $terms->get_error_message() ); } $result = []; foreach ( $terms as $term ) { $result[] = [ 'id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, 'count' => $term->count, ]; } wp_send_json_success( $result ); } /** * Enqueue admin CSS and JS */ public function enqueue_admin_assets( $hook ) { // Only load on our settings page (now it's a top-level menu) if ( 'toplevel_page_ai-search' !== $hook ) { return; } // Enqueue WordPress pointer for compatibility with other plugins wp_enqueue_script( 'wp-pointer' ); wp_enqueue_style( 'wp-pointer' ); wp_enqueue_style( 'ai-search-admin-settings', plugins_url( 'css/admin-settings.css', __FILE__ ), [], AI_SEARCH_VERSION ); } /** * Register settings menu in admin */ public function register_settings_menu() { // Main menu page add_menu_page( 'AI Search', // Page title 'AI Search', // Menu title 'manage_options', // Capability 'ai-search', // Menu slug [ $this->settings_pages, 'display_settings_page' ], // Callback AI_SEARCH_URL . 'assets/icon.svg', // Icon 58 // Position (before Settings) ); // Submenu pages add_submenu_page( 'ai-search', // Parent slug 'General Settings', // Page title 'General Settings', // Menu title 'manage_options', // Capability 'ai-search', // Menu slug (same as parent for first item) [ $this->settings_pages, 'display_settings_page' ] // Callback ); // The Setup Wizard submenu only makes sense until the user has // walked through it. After that it's noise: the same settings live // in the tabs, and a permanent "Setup Wizard" item next to the // General/Search/Indexing surface reads like the plugin still // isn't configured. The wizard URL keeps working for anyone who // navigates to it manually. if ( ! get_option( 'ai_search_setup_completed', false ) ) { add_submenu_page( 'ai-search', 'Setup Wizard', 'Setup Wizard', 'manage_options', 'ai-search&setup=wizard', [ $this->settings_pages, 'display_settings_page' ] ); } // Upgrade submenu — Free plan only, AI Service provider only. // * Paid customers already got what they were pitched, a permanent // "Upgrade" item after checkout reads as unresolved status. // * OpenAI (BYO key) users pay OpenAI directly and have no plan // concept in our service — showing them "Upgrade — 7-day trial" // would be misleading, so hide it. if ( 'ai_service' === get_option( 'ai_search_provider', 'ai_service' ) && $this->is_on_free_plan() ) { add_submenu_page( 'ai-search', __( 'Upgrade', 'ai-search' ), __( 'Upgrade: 7-day trial', 'ai-search' ), 'manage_options', 'ai-search-upgrade', [ $this, 'display_upgrade_page' ] ); } add_submenu_page( 'ai-search', 'Support Forum', 'Support Forum', 'manage_options', 'https://wordpress.org/support/plugin/ai-search/', '' ); add_submenu_page( 'ai-search', 'Contact the Author', 'Contact the Author', 'manage_options', 'https://samuelsilva.pt/lets-talk', '' ); } /** * Whether this site is currently on the Free plan. * * Reads from the quota transient the Quota Manager keeps (6h cache) — * NEVER makes an HTTP call from here, because admin_menu fires on * every wp-admin page load and blocking the sidebar on a 20s request * to Railway would tank the whole dashboard. If the transient is * empty, default to Free: worst case the paid customer sees an * unnecessary "Upgrade" item until the next quota refresh — mildly * annoying, not broken. * * @return bool */ private function is_on_free_plan() { $quota = get_transient( 'ai_search_quota_data' ); if ( ! is_array( $quota ) ) { return true; } $plan = isset( $quota['plan'] ) ? $quota['plan'] : null; $sub = isset( $quota['subscription'] ) ? $quota['subscription'] : null; $key = is_array( $plan ) && isset( $plan['key'] ) ? (string) $plan['key'] : 'free'; return ! ( 'free' !== $key && is_array( $sub ) ); } /** * Render the Upgrade page — a full-height iframe of the pricing page * in embed mode so the customer never leaves wp-admin. */ public function display_upgrade_page() { // `?minimal=1` triggers our own embed-mode CSS on wp-search.ai (hides // header/footer). Do NOT use `?embed=1` here: WordPress reserves that // query var for its oEmbed handler and would serve the minimal oEmbed // preview (title + excerpt + site name) instead of the actual page. $pricing_url = 'https://wp-search.ai/pricing/?minimal=1&utm_source=wp-admin&utm_medium=upgrade-menu'; ?>
:
: