* @author Maximum Software Inc * @license GPL-3.0-only * @link https://agenistrator.com/ * * SPDX-FileCopyrightText: 2025-2026 Alex Ruban * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc * SPDX-License-Identifier: GPL-3.0-only */ namespace Agenistrator; // Direct access prevention if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Admin class. */ class Admin implements Maintenance { use Singleton; /** * Plugin activation tasks for admin * @since 1.0.0 */ public function activate_plugin() { // Let the Settings and Chat classes handle their own activation $settings = Settings::get_instance(); if ( method_exists( $settings, 'activate_plugin' ) ) { $settings->activate_plugin(); } $chat = Chat::get_instance(); if ( method_exists( $chat, 'activate_plugin' ) ) { $chat->activate_plugin(); } } /** * Plugin deactivation tasks for admin * @since 1.0.0 */ public function deactivate_plugin() { // Let the Settings and Chat classes handle their own deactivation $settings = Settings::get_instance(); if ( method_exists( $settings, 'deactivate_plugin' ) ) { $settings->deactivate_plugin(); } $chat = Chat::get_instance(); if ( method_exists( $chat, 'deactivate_plugin' ) ) { $chat->deactivate_plugin(); } } /** * Uninstall plugin * @since 1.0.0 */ public function uninstall_plugin() { // Let the Settings and Chat classes handle their own uninstallation $settings = Settings::get_instance(); if ( method_exists( $settings, 'uninstall_plugin' ) ) { $settings->uninstall_plugin(); } $chat = Chat::get_instance(); if ( method_exists( $chat, 'uninstall_plugin' ) ) { $chat->uninstall_plugin(); } } /** * Register all admin hooks * @since 1.0.0 */ public function register_hooks() { // Admin UI hooks add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); // AJAX handlers add_action( 'wp_ajax_agenistrator_delete_report', array( $this, 'ajax_delete_report' ) ); add_action( 'wp_ajax_agenistrator_delete_all_reports', array( $this, 'ajax_delete_all_reports' ) ); // Let the Settings and Chat classes register their own hooks Settings::get_instance()->register_hooks(); Chat::get_instance()->register_hooks(); } /** * Load HTML templates for the admin common script * * @return array Associative array of template name => template content * @since 1.0.0 */ public function load_js_script_templates() { $templates = array(); // Define templates for the common script $script_templates = array( 'notice.html', ); $templates_dir = plugin_dir_path( __DIR__ ) . 'admin/html/'; foreach ( $script_templates as $template_file ) { $template_path = $templates_dir . $template_file; if ( file_exists( $template_path ) ) { // Get the template name without the .html extension for use as the key $template_name = basename( $template_file, '.html' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $templates[ $template_name ] = file_get_contents( $template_path ); } } return $templates; } /** * Get localized strings for the admin common script * * @return array Associative array of string keys => localized values * @since 1.0.0 */ public function get_js_strings() { // Define strings for the admin common script return array( // Console/debug messages for common.js 'invalid_templates_object' => __( 'Invalid templates object', 'agenistrator' ), 'template_not_found' => __( 'Template not found: {template_name}', 'agenistrator' ), // Common error messages 'unknown_error' => __( 'Unknown error', 'agenistrator' ), ); } /** * Register admin styles and scripts * * @param string $hook The current admin page hook * @since 1.0.0 */ public function enqueue_scripts( $hook ) { // Register common CSS wp_register_style( 'agenistrator-common', plugin_dir_url( __FILE__ ) . 'css/common.css', array(), AGENISTRATOR_VERSION ); // Register common utilities script wp_register_script( 'agenistrator-common', plugin_dir_url( __FILE__ ) . 'js/common.js', array( 'jquery' ), AGENISTRATOR_VERSION, true ); // Register progress bar script wp_register_script( 'agenistrator-progress', plugin_dir_url( __FILE__ ) . 'js/progress.js', array( 'jquery', 'agenistrator-common' ), AGENISTRATOR_VERSION, true ); // Get localized strings for the common script $strings = $this->get_js_strings(); // Get templates for the common script $templates = $this->load_js_script_templates(); // Prepare data for the common script $script_data = array( 'nonce' => wp_create_nonce( 'agenistrator_nonce' ), 'strings' => $strings, 'templates' => $templates, ); // Localize and enqueue the common script wp_localize_script( 'agenistrator-common', 'agenistrator', $script_data ); wp_enqueue_script( 'agenistrator-common' ); // Enqueue scripts and styles for the status page if ( 'toplevel_page_agenistrator' === $hook ) { // Register admin CSS with common CSS as dependency wp_register_style( 'agenistrator-admin', plugin_dir_url( __FILE__ ) . 'css/admin.css', array( 'agenistrator-common' ), AGENISTRATOR_VERSION ); wp_enqueue_style( 'agenistrator-admin' ); // Register and enqueue status page script wp_register_script( 'agenistrator-status', plugin_dir_url( __FILE__ ) . 'js/status.js', array( 'jquery', 'agenistrator-common' ), AGENISTRATOR_VERSION, true ); wp_localize_script( 'agenistrator-status', 'agenistrator_status', array( 'strings' => array( 'confirm_delete_report' => __( 'Are you sure you want to delete this report?', 'agenistrator' ), 'confirm_delete_all_reports' => __( 'Are you sure you want to delete all reports?', 'agenistrator' ), 'failed_to_delete_report' => __( 'Failed to delete report', 'agenistrator' ), 'failed_to_delete_reports' => __( 'Failed to delete reports', 'agenistrator' ), ), ) ); wp_enqueue_script( 'agenistrator-status' ); // Enqueue progress bar script wp_enqueue_script( 'agenistrator-progress' ); } } /** * Add admin menu and global interface * @since 1.0.0 */ public function add_admin_menu() { // Main menu add_menu_page( __( 'Agenistrator Status', 'agenistrator' ), __( 'Agenistrator', 'agenistrator' ), 'manage_options', 'agenistrator', array( $this, 'display_status_page' ), 'dashicons-businessman', 30 ); // Status submenu (replaces auto-generated first submenu item) add_submenu_page( 'agenistrator', __( 'Agenistrator Status', 'agenistrator' ), __( 'Status', 'agenistrator' ), 'manage_options', 'agenistrator', array( $this, 'display_status_page' ) ); // Settings submenu - delegate to Settings class Settings::get_instance()->add_settings_page(); // About submenu add_submenu_page( 'agenistrator', __( 'About Agenistrator', 'agenistrator' ), __( 'About', 'agenistrator' ), 'manage_options', 'agenistrator-about', array( $this, 'display_about_page' ) ); } /** * Display status page * @since 1.0.0 */ public function display_status_page() { // Check if embeddings are supported $is_embeddings_supported = Embeddings_Manager::get_instance()->is_database_compatible(); // Get plugin status info - only fetch count if embeddings are supported $embedding_count = $is_embeddings_supported ? Embeddings_Manager::get_instance()->get_embeddings_count( 'content' ) : 0; // Get chat model information try { $chat_model_obj = LLM_Manager::get_instance()->get_chat_model(); $chat_model = $chat_model_obj->get_model_id(); $chat_status_text = __( 'Configured', 'agenistrator' ); $chat_status_color = 'green'; } catch ( \Throwable $e ) { $chat_model = __( 'Not configured', 'agenistrator' ); $chat_status_text = __( 'Not configured', 'agenistrator' ); $chat_status_color = 'red'; } // Get embeddings model information if ( ! $is_embeddings_supported ) { // Embeddings not supported by the current database $embeddings_model = __( 'Not available', 'agenistrator' ); $embeddings_status_text = __( 'Not supported', 'agenistrator' ); $embeddings_status_color = 'gray'; } else { try { $embeddings_model_obj = LLM_Manager::get_instance()->get_embedding_model(); $embeddings_model = $embeddings_model_obj->get_model_id(); $embeddings_status_text = __( 'Configured', 'agenistrator' ); $embeddings_status_color = 'green'; } catch ( \Throwable $e ) { $embeddings_model = __( 'Not configured', 'agenistrator' ); $embeddings_status_text = __( 'Not configured', 'agenistrator' ); $embeddings_status_color = 'red'; } } // Render embeddings progress bars (sync + cleanup) $embeddings_progress_bars = $this->render_progress_bars( 'embeddings' ) . $this->render_progress_bars( 'embeddings_cleanup' ); // Prepare attributes for the template $attributes = array( 'page_title' => esc_html( get_admin_page_title() ), 'settings_url' => esc_url( admin_url( 'admin.php?page=agenistrator-settings' ) ), 'chat_model' => esc_html( $chat_model ), 'chat_status_text' => esc_html( $chat_status_text ), 'chat_status_color' => esc_attr( $chat_status_color ), 'embeddings_model' => esc_html( $embeddings_model ), 'embeddings_status_text' => esc_html( $embeddings_status_text ), 'embeddings_status_color' => esc_attr( $embeddings_status_color ), 'system_status_title' => esc_html__( 'System Status', 'agenistrator' ), 'chat_model_label' => esc_html__( 'Chat Model:', 'agenistrator' ), 'embeddings_model_label' => esc_html__( 'Embeddings Model:', 'agenistrator' ), 'manage_settings_button' => esc_html__( 'Manage Settings', 'agenistrator' ), 'embeddings_title' => esc_html__( 'Embeddings', 'agenistrator' ), 'total_embeddings_label' => esc_html__( 'Total Embeddings:', 'agenistrator' ), 'total_embeddings_count' => $is_embeddings_supported ? esc_html( Embeddings_Manager::get_instance()->get_embeddings_count() ) : esc_html__( 'N/A', 'agenistrator' ), 'content_embeddings_label' => esc_html__( 'Content Embeddings:', 'agenistrator' ), 'content_embeddings_count' => $is_embeddings_supported ? esc_html( $embedding_count ) : esc_html__( 'N/A', 'agenistrator' ), 'embeddings_progress_bars' => $embeddings_progress_bars, 'embeddings_description' => esc_html__( 'Embeddings enable semantic search for WordPress content. New content and content changes are automatically processed.', 'agenistrator' ), 'embeddings_url' => esc_url( admin_url( 'admin.php?page=agenistrator-settings#embeddings' ) ), 'manage_embeddings_button' => esc_html__( 'Manage Embeddings', 'agenistrator' ), 'available_tools_title' => esc_html__( 'Available Tools', 'agenistrator' ), 'tools_url' => esc_url( admin_url( 'admin.php?page=agenistrator-settings#tools' ) ), 'manage_tools_button' => esc_html__( 'Manage Tools', 'agenistrator' ), ); // Generate tool stats and names list using Tool Manager $tool_manager = Tool_Manager::get_instance(); $tools = $tool_manager->get_tools(); // Calculate tool stats $total_count = count( $tools ); $enabled_count = 0; $core_count = 0; $read_only_total = 0; $read_only_enabled = 0; $read_write_total = 0; $read_write_enabled = 0; foreach ( $tools as $tool_id => $tool ) { if ( $tool->is_always_imported() ) { ++$core_count; } $is_enabled = $tool_manager->is_tool_enabled( $tool_id ); if ( $is_enabled ) { ++$enabled_count; } if ( $tool->is_read_only() ) { ++$read_only_total; if ( $is_enabled ) { ++$read_only_enabled; } } else { ++$read_write_total; if ( $is_enabled ) { ++$read_write_enabled; } } } // Add tool stats to attributes $attributes['total_tools_label'] = esc_html__( 'Total Tools:', 'agenistrator' ); $attributes['total_tools_count'] = esc_html( $total_count ); $attributes['enabled_tools_label'] = esc_html__( 'Enabled Tools:', 'agenistrator' ); $attributes['enabled_tools_count'] = esc_html( $enabled_count ); $attributes['core_tools_label'] = esc_html__( 'Core Tools:', 'agenistrator' ); $attributes['core_tools_count'] = esc_html( $core_count ); $attributes['read_only_tools_label'] = esc_html__( 'Read-Only Tools:', 'agenistrator' ); $attributes['read_only_total_count'] = esc_html( $read_only_total ); $attributes['read_only_enabled_count'] = esc_html( $read_only_enabled ); $attributes['read_write_tools_label'] = esc_html__( 'Read-Write Tools:', 'agenistrator' ); $attributes['read_write_total_count'] = esc_html( $read_write_total ); $attributes['read_write_enabled_count'] = esc_html( $read_write_enabled ); // Render the reports card $attributes['reports_card'] = $this->render_reports_card(); // Render the template // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Template system outputs pre-escaped HTML; all $attributes values are escaped above echo String_Utils::render( 'dashboard', $attributes ); } /** * Display about page * * @since 1.0.0 */ public function display_about_page() { $attributes = array( 'page_title' => esc_html( get_admin_page_title() ), 'plugin_info_title' => esc_html__( 'Plugin Information', 'agenistrator' ), 'version_label' => esc_html__( 'Version:', 'agenistrator' ), 'version' => esc_html( AGENISTRATOR_VERSION ), 'authors_label' => esc_html__( 'Authors:', 'agenistrator' ), 'website_label' => esc_html__( 'Website:', 'agenistrator' ), 'support_label' => esc_html__( 'Support:', 'agenistrator' ), 'license_title' => esc_html__( 'License', 'agenistrator' ), 'license_text' => String_Utils::replace_tags( /* translators: {a-href} and {/a} are HTML link tags wrapping the license name */ esc_html__( 'This plugin is free software licensed under the {a-href}GNU General Public License v3.0{/a}.', 'agenistrator' ), array( 'a-href' => '', '/a' => '', ) ), 'view_full_license' => String_Utils::replace_tags( /* translators: {a-href} and {/a} are HTML link tags */ esc_html__( '{a-href}View Full License Text{/a}', 'agenistrator' ), array( 'a-href' => '', '/a' => '', ) ), 'warranty_title' => esc_html__( 'Warranty Disclaimer', 'agenistrator' ), 'credits_title' => esc_html__( 'Credits', 'agenistrator' ), 'developed_by' => String_Utils::replace_tags( /* translators: {a-href} and {/a} are HTML link tags wrapping the developer name */ esc_html__( 'Developed by {a-href}Maximum.Software{/a}', 'agenistrator' ), array( 'a-href' => '', '/a' => '', ) ), 'credits_desc' => esc_html__( 'Agenistrator is built with open source libraries and tools. We thank all contributors to the projects that make this plugin possible.', 'agenistrator' ), ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Template system outputs pre-escaped HTML; all $attributes values are escaped above echo String_Utils::render( 'about', $attributes ); } /** * Render the error reports card * * @return string Rendered HTML for the reports card */ private function render_reports_card() { $reporter = Reporter::get_instance(); $reports = $reporter->list_reports(); // Hide card when no reports exist if ( empty( $reports ) ) { return ''; } // Render report rows $report_rows = ''; foreach ( $reports as $report ) { $download_url = $reporter->get_report_url( $report['report_id'] ); $report_rows .= String_Utils::render( 'report-list-item', array( 'report_id' => esc_attr( $report['report_id'] ), 'size' => esc_html( size_format( $report['size'] ) ), 'created' => esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $report['created'] ) ), 'download_url' => esc_url( $download_url ), 'download_button' => esc_html__( 'Download', 'agenistrator' ), 'delete_button' => esc_html__( 'Delete', 'agenistrator' ), ) ); } return String_Utils::render( 'report-card', array( 'reports_title' => esc_html__( 'Error Reports', 'agenistrator' ), 'reports_description' => esc_html__( 'Reports generated from the chat widget for debugging.', 'agenistrator' ), 'report_id_header' => esc_html__( 'Report ID', 'agenistrator' ), 'size_header' => esc_html__( 'Size', 'agenistrator' ), 'created_header' => esc_html__( 'Created', 'agenistrator' ), 'actions_header' => esc_html__( 'Actions', 'agenistrator' ), 'report_rows' => $report_rows, 'delete_all_button' => esc_html__( 'Delete All', 'agenistrator' ), ) ); } /** * AJAX handler for deleting a report * @since 1.0.0 */ public function ajax_delete_report() { // Verify nonce check_ajax_referer( 'agenistrator_nonce', 'nonce' ); try { // Check permissions if ( ! current_user_can( 'manage_options' ) ) { throw new Ajax_Exception( __( 'You do not have permission to perform this action.', 'agenistrator' ), 403 ); } $report_id = isset( $_POST['report_id'] ) ? sanitize_key( wp_unslash( $_POST['report_id'] ) ) : ''; if ( empty( $report_id ) ) { throw new Ajax_Exception( __( 'Invalid report ID', 'agenistrator' ), 400 ); } Reporter::get_instance()->delete_report( $report_id ); wp_send_json_success(); } catch ( \Throwable $e ) { WordPress_Utils::send_json_exception( $e ); } } /** * AJAX handler for deleting all reports * @since 1.0.0 */ public function ajax_delete_all_reports() { // Verify nonce check_ajax_referer( 'agenistrator_nonce', 'nonce' ); try { // Check permissions if ( ! current_user_can( 'manage_options' ) ) { throw new Ajax_Exception( __( 'You do not have permission to perform this action.', 'agenistrator' ), 403 ); } Reporter::get_instance()->delete_all_reports(); wp_send_json_success(); } catch ( \Throwable $e ) { WordPress_Utils::send_json_exception( $e ); } } /** * Render progress bars for all running operations of a given type. * * @param string $operation The operation identifier (e.g., 'embeddings'). * @return string Rendered HTML for all progress bars, or empty string if none running. * @since 1.0.0 */ public function render_progress_bars( $operation ) { $trackers = Progress_Tracker::get_running( $operation ); if ( empty( $trackers ) ) { return ''; } $labels = array( 'embeddings' => array( 'title' => __( 'Syncing Embeddings...', 'agenistrator' ), 'count_format' => __( '{current} of {total} posts processed', 'agenistrator' ), ), 'embeddings_cleanup' => array( 'title' => __( 'Cleaning up orphan embeddings...', 'agenistrator' ), 'count_format' => __( '{current} of {total} vectors checked', 'agenistrator' ), ), ); $default_labels = array( 'title' => __( 'Processing...', 'agenistrator' ), 'count_format' => __( '{current} of {total} items processed', 'agenistrator' ), ); $output = ''; foreach ( $trackers as $tracker ) { $op = $tracker->get_operation(); $progress_labels = isset( $labels[ $op ] ) ? $labels[ $op ] : $default_labels; $progress_text = String_Utils::replace_tags( $progress_labels['count_format'], array( 'current' => $tracker->get_current(), 'total' => $tracker->get_total(), ) ); $output .= String_Utils::render( 'progress-bar', array( 'instance_id' => esc_attr( $tracker->get_instance_id() ), 'progress_title' => esc_html( $progress_labels['title'] ), 'progress_percent' => esc_attr( $tracker->get_percent_complete() ), 'progress_text' => esc_html( $progress_text ), 'cancel_button' => esc_html__( 'Cancel', 'agenistrator' ), ) ); } return $output; } }