settings = $settings; } /** * Register WordPress hooks for admin bar and dashboard widget. */ public function init() { if ( ! $this->settings->get( 'enabled' ) || ! $this->settings->has_domain_key() ) { return; } // Admin bar badge (priority 100 to appear after default items). add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_badge' ), 100 ); // Dashboard widget. add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widget' ) ); // Enqueue admin bar styles on all admin pages. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); // Also show admin bar on the front end for logged-in admins. add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); } /** * Enqueue admin bar + widget styles. */ public function enqueue_styles() { if ( ! is_admin_bar_showing() && ! is_admin() ) { return; } wp_enqueue_style( 'aiadvisors-score', AIADVISORS_URL . 'assets/css/score.css', array(), AIADVISORS_VERSION ); } /** * Add AEO score badge to the WordPress admin bar. * * @param \WP_Admin_Bar $wp_admin_bar Admin bar instance. */ public function add_admin_bar_badge( $wp_admin_bar ) { if ( ! current_user_can( 'manage_options' ) ) { return; } $data = $this->get_score_data(); if ( ! $data || $data['aeo_score'] === null ) { // No score yet - show prompt to run audit. $wp_admin_bar->add_node( array( 'id' => 'aiadvisors-score', 'title' => '✦ AEO: --', 'href' => $data['platform_url'] ?? 'https://app.ai-advisors.ai/dashboard/aeo', 'meta' => array( 'class' => 'aiadvisors-admin-bar-item', 'title' => __( 'No AEO audit yet. Click to run one.', 'ai-advisors' ), 'target' => '_blank', ), ) ); return; } $score = (int) $data['aeo_score']; $delta = (int) ( $data['score_delta'] ?? 0 ); $color_class = $this->get_score_color_class( $score ); // Build label. $delta_str = ''; if ( $delta > 0 ) { $delta_str = ' +' . $delta . ''; } elseif ( $delta < 0 ) { $delta_str = ' ' . $delta . ''; } $title = sprintf( '✦ %d%s', esc_attr( $color_class ), $score, $delta_str ); $wp_admin_bar->add_node( array( 'id' => 'aiadvisors-score', 'title' => $title, 'href' => $data['platform_url'] ?? 'https://app.ai-advisors.ai/dashboard/aeo', 'meta' => array( 'class' => 'aiadvisors-admin-bar-item', 'title' => sprintf( /* translators: %d: AEO score */ __( 'AEO Score: %d/100. Click to view details.', 'ai-advisors' ), $score ), 'target' => '_blank', ), ) ); // Add sub-items with breakdown. if ( ! empty( $data['issues_count'] ) ) { $wp_admin_bar->add_node( array( 'parent' => 'aiadvisors-score', 'id' => 'aiadvisors-issues', 'title' => sprintf( /* translators: %d: number of issues */ __( '%d open issues', 'ai-advisors' ), (int) $data['issues_count'] ), 'href' => 'https://app.ai-advisors.ai/dashboard/aeo/recommendations', 'meta' => array( 'target' => '_blank' ), ) ); } $wp_admin_bar->add_node( array( 'parent' => 'aiadvisors-score', 'id' => 'aiadvisors-dashboard-link', 'title' => __( 'Open AI-Advisors', 'ai-advisors' ), 'href' => 'https://app.ai-advisors.ai/dashboard', 'meta' => array( 'target' => '_blank' ), ) ); } /** * Register the dashboard widget. */ public function register_dashboard_widget() { if ( ! current_user_can( 'manage_options' ) ) { return; } wp_add_dashboard_widget( 'aiadvisors_score_widget', '✦ ' . esc_html__( 'AI-Advisors', 'ai-advisors' ) . '', array( $this, 'render_dashboard_widget' ) ); } /** * Render the dashboard widget content. */ public function render_dashboard_widget() { $data = $this->get_score_data(); if ( ! $data || $data['aeo_score'] === null ) { echo '

' . esc_html__( 'No AEO audit data yet.', 'ai-advisors' ) . '

'; echo '

'; echo esc_html__( 'Run your first audit', 'ai-advisors' ); echo ' →

'; return; } $score = (int) $data['aeo_score']; $delta = (int) ( $data['score_delta'] ?? 0 ); $issues = (int) ( $data['issues_count'] ?? 0 ); $color_class = $this->get_score_color_class( $score ); $last_scan = $data['last_scan'] ? wp_date( 'M j, Y', strtotime( $data['last_scan'] ) ) : '—'; ?>
0 ? '+' . $delta : (string) $delta ); ?>
cache_key ); if ( $cached !== false ) { if ( is_array( $cached ) && ! empty( $cached['aiadvisors_error'] ) ) { return null; } return $cached; } // Fetch from platform API. $domain_key = $this->settings->get_domain_key(); // Consent for platform calls is granted by connecting a domain key. if ( empty( $domain_key ) ) { return null; } $endpoint = rtrim( $this->settings->get( 'endpoint', AIADVISORS_API_ENDPOINT ), '/' ); // The score endpoint is on the same domain as collect, just different path. $base_url = preg_replace( '#/api/collect$#', '', $endpoint ); // The /api/wordpress/* endpoints authenticate via the domain_key query // parameter; the Bearer header is also sent for forward compatibility. $url = add_query_arg( 'domain_key', rawurlencode( $domain_key ), $base_url . '/api/wordpress/score' ); $response = wp_remote_get( $url, array( 'timeout' => 8, 'sslverify' => true, 'headers' => array( 'Authorization' => 'Bearer ' . $domain_key ), 'user-agent' => 'AI-Advisors WordPress Plugin/' . AIADVISORS_VERSION, ) ); if ( is_wp_error( $response ) ) { return $this->cache_failure(); } $code = wp_remote_retrieve_response_code( $response ); if ( $code !== 200 ) { return $this->cache_failure(); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( ! is_array( $data ) ) { return $this->cache_failure(); } // Cache the result. set_transient( $this->cache_key, $data, $this->cache_ttl ); return $data; } /** * Cache a short-lived "fetch failed" marker and return null. Keeps repeated * admin renders (admin-bar badge + dashboard widget, plus the front-end bar * for logged-in admins) from each making a fresh blocking request while the * platform is unreachable. * * @return null */ private function cache_failure() { set_transient( $this->cache_key, array( 'aiadvisors_error' => true ), 2 * MINUTE_IN_SECONDS ); return null; } /** * Clear the cached score (called when user runs a new audit or wants fresh data). */ public static function clear_cache() { delete_transient( 'aiadvisors_score_data' ); } /** * Get CSS class for score color (stoplight: green 80+, yellow 61-79, red 0-60). * * @param int $score AEO score 0-100. * @return string CSS class name. */ private function get_score_color_class( $score ) { if ( $score >= 80 ) { return 'aiadvisors-score-green'; } elseif ( $score >= 61 ) { return 'aiadvisors-score-yellow'; } return 'aiadvisors-score-red'; } }