connect = $connect; } /** * Hook everything up. */ public function register_hooks() { add_action( 'admin_menu', array( $this, 'register_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'wp_ajax_ael42a_run_checks', array( $this, 'ajax_run_checks' ) ); add_action( 'wp_ajax_ael42a_refresh_vitals', array( $this, 'ajax_refresh_vitals' ) ); add_action( 'wp_ajax_ael42a_disconnect', array( $this, 'ajax_disconnect' ) ); add_action( 'wp_ajax_ael42a_save_settings', array( $this, 'ajax_save_settings' ) ); } /** * Register the top-level menu page. * * The menu icon is the 42A logo mark as a base64 SVG data URI (WordPress * only recolors dashicons, so custom SVGs keep their own fills - the * brand colors read fine on the dark admin sidebar). The wide mark is * centered in a square viewBox so the 20px menu slot renders it whole. */ public function register_menu() { $menu_icon = 'data:image/svg+xml;base64,' . base64_encode( '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '' . '' ); $this->hook_suffix = add_menu_page( __( '42A AEL - Agentic Enablement Layer', 'ael-42a' ), __( '42A AEL', 'ael-42a' ), 'manage_options', self::SLUG, array( $this, 'render_page' ), $menu_icon, 81 ); } /** * Enqueue admin assets only on our page. * * @param string $hook Current admin page hook. */ public function enqueue_assets( $hook ) { if ( $hook !== $this->hook_suffix ) { return; } wp_enqueue_style( 'ael42a-admin', AEL42A_URL . 'admin/css/admin.css', array(), AEL42A_VERSION ); wp_enqueue_script( 'ael42a-admin', AEL42A_URL . 'admin/js/admin.js', array(), AEL42A_VERSION, true ); wp_localize_script( 'ael42a-admin', 'ael42a', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE ), 'i18n' => array( 'running' => __( 'Running checks...', 'ael-42a' ), 'checkStatus' => __( 'Status', 'ael-42a' ), 'checkName' => __( 'Check', 'ael-42a' ), 'checkDetails' => __( 'Details', 'ael-42a' ), 'copied' => __( 'Copied!', 'ael-42a' ), 'copy' => __( 'Copy', 'ael-42a' ), 'error' => __( 'Something went wrong. Please try again.', 'ael-42a' ), 'saved' => __( 'Settings saved.', 'ael-42a' ), 'confirmDisconnect' => __( 'Disconnect this site from 42A? The vitals report will no longer be available.', 'ael-42a' ), ), ) ); } /** * Render the page shell + active tab view. */ public function render_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to access this page.', 'ael-42a' ) ); } $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'agent-layer'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only routing. if ( ! in_array( $tab, array( 'agent-layer', 'visibility', 'vitals', 'account', 'contact' ), true ) ) { $tab = 'agent-layer'; } $base_url = admin_url( 'admin.php?page=' . self::SLUG ); echo '
'; // Branded header: static inline 42A logo mark (no remote assets in // wp-admin). Kept as the page

so WP core relocates admin notices // directly below it. echo '

'; echo ''; echo '' . esc_html__( '42A AEL', 'ael-42a' ) . ''; echo '' . esc_html__( 'Agentic Enablement Layer', 'ael-42a' ) . ''; echo '

'; if ( isset( $_GET['connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only notice. echo '

' . esc_html__( 'Site connected to 42A. Domain verification is in progress.', 'ael-42a' ) . '

'; } if ( isset( $_GET['connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only notice. $connect_error = sanitize_text_field( wp_unslash( $_GET['connect_error'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only notice. $connect_error = sanitize_text_field( rawurldecode( $connect_error ) ); echo '

' . esc_html( sprintf( /* translators: %s: error message. */ __( 'Connection failed: %s', 'ael-42a' ), $connect_error ) ) . '

'; } echo ''; // Two-column layout: tab content on the left; while not connected, a // blurred GEO teaser on the right nudging toward the connect flow. echo '
'; echo '
'; if ( 'vitals' === $tab ) { $this->render_vitals_tab(); } elseif ( 'visibility' === $tab ) { $this->render_visibility_tab(); } elseif ( 'account' === $tab ) { $this->render_account_tab(); } elseif ( 'contact' === $tab ) { $this->render_contact_tab(); } else { $this->render_agent_layer_tab(); } echo '
'; if ( ! $this->connect->is_connected() ) { $this->render_geo_teaser(); } elseif ( 'agent-layer' === $tab ) { $this->render_visibility_sidebar( $base_url ); } echo '
'; echo '
'; } /** * Blurred GEO teaser sidebar (not-connected state only). */ private function render_geo_teaser() { $connect_url = $this->connect->connect_url(); require AEL42A_DIR . 'admin/views/geo-teaser.php'; } /** * Live AI-visibility sidebar on the Agent Layer tab (connected state) - * same slot the blurred teaser occupies before connecting. * * @param string $base_url Plugin settings page base URL. */ private function render_visibility_sidebar( $base_url ) { $result = $this->connect->get_visibility_cached(); $visibility = is_wp_error( $result ) ? null : $result; $visibility_tab_url = add_query_arg( 'tab', 'visibility', $base_url ); $dashboard_url = add_query_arg( array( 'utm_source' => 'wordpress-plugin', 'utm_medium' => 'admin', 'utm_campaign' => 'ael-sidebar', ), $this->connect->api()->app_url() . '/dashboard' ); require AEL42A_DIR . 'admin/views/visibility-sidebar.php'; } /** * Agent Layer tab. */ private function render_agent_layer_tab() { $telemetry = new Telemetry(); $sample_post = get_posts( array( 'post_type' => array( 'page', 'post' ), 'post_status' => 'publish', 'posts_per_page' => 1, ) ); $markdown_example = ! empty( $sample_post ) ? add_query_arg( 'format', 'markdown', get_permalink( $sample_post[0] ) ) : home_url( '/?format=markdown' ); $endpoints = array( array( 'label' => __( 'Markdown (any page)', 'ael-42a' ), 'url' => $markdown_example, ), array( 'label' => 'llms.txt', 'url' => home_url( '/llms.txt' ), ), array( 'label' => 'llms-full.txt', 'url' => home_url( '/llms-full.txt' ), ), array( 'label' => __( 'MCP endpoint', 'ael-42a' ), 'url' => rest_url( '42a/v1/mcp' ), ), array( 'label' => __( 'MCP discovery', 'ael-42a' ), 'url' => home_url( '/.well-known/mcp.json' ), ), ); $traffic = $telemetry->recent_days( 7 ); $bot_traffic = $telemetry->bot_traffic_summary( 7 ); $telemetry_opt_in = $telemetry->is_opted_in(); $full_limit = (int) get_option( 'ael42a_full_limit', Llms_Txt::FULL_ITEM_LIMIT ); require AEL42A_DIR . 'admin/views/agent-layer.php'; } /** * AI Visibility tab: read-only dashboard mirror (stats, charts, projects). */ private function render_visibility_tab() { $connect = $this->connect; $connected = $connect->is_connected(); $visibility = null; $visibility_error = ''; if ( $connected ) { $result = $connect->get_visibility_cached(); if ( is_wp_error( $result ) ) { $visibility_error = $result->get_error_message(); } else { $visibility = $result; } } $connect_url = $connect->connect_url(); $app_url = $connect->api()->app_url(); $dashboard_url = add_query_arg( array( 'utm_source' => 'wordpress-plugin', 'utm_medium' => 'admin', 'utm_campaign' => 'ael-visibility', ), $app_url . '/dashboard' ); require AEL42A_DIR . 'admin/views/ai-visibility.php'; } /** * Account tab: 42A connection management (connect, status, disconnect). */ private function render_account_tab() { $connect = $this->connect; $connected = $connect->is_connected(); $snapshot = $connect->get_site_snapshot(); $connect_url = $connect->connect_url(); $app_url = $connect->api()->app_url(); $site_origin = home_url( '/' ); $plugin_version = AEL42A_VERSION; require AEL42A_DIR . 'admin/views/account.php'; } /** * Contact tab: direct support email + a client-side mailto composer. * * No form data ever touches the server or the 42A platform - the * "Compose email" button builds a mailto: link in the browser and hands * off to the admin's own mail client, so it works on every host with no * SMTP configuration and no risk of silent delivery failures. */ private function render_contact_tab() { $support_email = 'support@42a.ai'; /* translators: %s: site name. */ $default_subject = sprintf( __( '[42A] Support request from %s', 'ael-42a' ), get_bloginfo( 'name' ) ); // Diagnostic footer appended to the email body so support has the basics // without a back-and-forth. Built server-side (translated labels) and // passed to JS, which URL-encodes it into the mailto body. $snapshot = $this->connect->get_site_snapshot(); $org = isset( $snapshot['org'] ) && is_array( $snapshot['org'] ) ? $snapshot['org'] : array(); $brand = isset( $snapshot['brand'] ) && is_array( $snapshot['brand'] ) ? $snapshot['brand'] : array(); $context_lines = array( /* translators: %s: site URL. */ sprintf( __( 'Site: %s', 'ael-42a' ), home_url( '/' ) ), /* translators: %s: plugin version. */ sprintf( __( 'Plugin version: %s', 'ael-42a' ), AEL42A_VERSION ), /* translators: %s: WordPress version. */ sprintf( __( 'WordPress version: %s', 'ael-42a' ), get_bloginfo( 'version' ) ), ); if ( $this->connect->is_connected() ) { if ( ! empty( $org['name'] ) ) { /* translators: %s: 42A organization name. */ $context_lines[] = sprintf( __( 'Organization: %s', 'ael-42a' ), (string) $org['name'] ); } if ( ! empty( $brand['name'] ) ) { /* translators: %s: 42A brand name. */ $context_lines[] = sprintf( __( 'Brand: %s', 'ael-42a' ), (string) $brand['name'] ); } } $context_block = __( 'Sent from the 42A WordPress plugin', 'ael-42a' ) . "\n" . implode( "\n", $context_lines ); require AEL42A_DIR . 'admin/views/contact.php'; } /** * GEO Vitals tab. */ private function render_vitals_tab() { $connect = $this->connect; $connected = $connect->is_connected(); $snapshot = $connect->get_site_snapshot(); $vitals = null; $vitals_error = ''; if ( $connected ) { $result = $connect->get_vitals_cached(); if ( is_wp_error( $result ) ) { $vitals_error = $result->get_error_message(); } else { $vitals = $result; } } $connect_url = $connect->connect_url(); $app_url = $connect->api()->app_url(); $upsell_url = add_query_arg( array( 'utm_source' => 'wordpress-plugin', 'utm_medium' => 'admin', 'utm_campaign' => 'ael-vitals', ), $app_url ); require AEL42A_DIR . 'admin/views/vitals.php'; } // ----------------------------------------------------------------- AJAX. /** * Guard shared by every AJAX handler. */ private function guard_ajax() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'ael-42a' ) ), 403 ); } check_ajax_referer( self::NONCE, 'nonce' ); } /** * Run local vitals checks. */ public function ajax_run_checks() { $this->guard_ajax(); $force = isset( $_POST['force'] ) && '1' === $_POST['force']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Missing -- compared against fixed string; nonce checked in guard_ajax(). $vitals = new Local_Vitals(); wp_send_json_success( array( 'checks' => $vitals->run_checks( $force ), ) ); } /** * Clear + refetch the connected vitals report. */ public function ajax_refresh_vitals() { $this->guard_ajax(); delete_transient( Cache::VITALS ); $this->connect->refresh_site_snapshot(); // The snapshot refresh may have discovered a platform-side disconnect // and wiped local state - report success so the page reloads into the // reconnect flow instead of showing a dead-end error. if ( ! $this->connect->is_connected() ) { wp_send_json_success( array( 'disconnected' => true ) ); } $result = $this->connect->get_vitals_cached( true ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'vitals' => $result ) ); } /** * Disconnect from 42A. */ public function ajax_disconnect() { $this->guard_ajax(); $this->connect->disconnect(); wp_send_json_success( array( 'message' => __( 'Disconnected from 42A.', 'ael-42a' ) ) ); } /** * Save settings (telemetry opt-in, llms-full item count). */ public function ajax_save_settings() { $this->guard_ajax(); $opt_in = isset( $_POST['telemetry_opt_in'] ) && '1' === $_POST['telemetry_opt_in'] ? '1' : '0'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Missing -- compared against fixed string; nonce checked in guard_ajax(). update_option( Telemetry::OPT_IN, $opt_in, false ); if ( isset( $_POST['full_limit'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked in guard_ajax(). $limit = max( 1, min( Llms_Txt::FULL_ITEM_LIMIT, (int) $_POST['full_limit'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Missing -- cast to int; nonce checked in guard_ajax(). update_option( 'ael42a_full_limit', $limit, false ); delete_transient( Cache::LLMS_FULL ); } wp_send_json_success( array( 'message' => __( 'Settings saved.', 'ael-42a' ) ) ); } }