*/ private $ranges = array(); /** * Page hook suffixes for asset scoping. * * @var string[] */ private $hooks = array(); public function __construct() { $this->ranges = array( '24h' => __( 'Last 24h', 'aiso-ai-crawler-analytics' ), '7d' => __( 'Last 7 days', 'aiso-ai-crawler-analytics' ), '30d' => __( 'Last 30 days', 'aiso-ai-crawler-analytics' ), '90d' => __( 'Last 90 days', 'aiso-ai-crawler-analytics' ), 'all' => __( 'All time', 'aiso-ai-crawler-analytics' ), ); add_action( 'admin_menu', array( $this, 'menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'assets' ) ); add_filter( 'plugin_action_links_' . plugin_basename( AISO_FILE ), array( $this, 'action_links' ) ); } /** * Register the top-level menu + sub-pages. */ public function menu() { $this->hooks['dashboard'] = add_menu_page( __( 'AI Crawler Analytics', 'aiso-ai-crawler-analytics' ), __( 'AI Crawlers', 'aiso-ai-crawler-analytics' ), 'manage_options', 'aiso-dashboard', array( $this, 'render_dashboard' ), 'dashicons-chart-area', 58 ); add_submenu_page( 'aiso-dashboard', __( 'AI Crawler Analytics', 'aiso-ai-crawler-analytics' ), __( 'Dashboard', 'aiso-ai-crawler-analytics' ), 'manage_options', 'aiso-dashboard', array( $this, 'render_dashboard' ) ); $this->hooks['settings'] = add_submenu_page( 'aiso-dashboard', __( 'Aiso Settings', 'aiso-ai-crawler-analytics' ), __( 'Settings', 'aiso-ai-crawler-analytics' ), 'manage_options', 'aiso-settings', array( $this, 'render_settings' ) ); } /** * "Settings" shortcut on the Plugins screen. * * @param string[] $links Existing links. * @return string[] */ public function action_links( $links ) { $url = admin_url( 'admin.php?page=aiso-settings' ); array_unshift( $links, '' . esc_html__( 'Settings', 'aiso-ai-crawler-analytics' ) . '' ); return $links; } /** * Enqueue our scoped styles on plugin screens only. * * @param string $hook Current admin page hook. */ public function assets( $hook ) { if ( ! in_array( $hook, $this->hooks, true ) ) { return; } wp_enqueue_style( 'aiso-admin', AISO_URL . 'admin/css/aiso-admin.css', array(), AISO_VERSION ); } /** * Current range from the query string, validated against the whitelist. * * @return string */ private function current_range() { $range = isset( $_GET['range'] ) ? sanitize_key( wp_unslash( $_GET['range'] ) ) : '7d'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only navigation. return isset( $this->ranges[ $range ] ) ? $range : '7d'; } // --------------------------------------------------------------------- // Dashboard // --------------------------------------------------------------------- /** * Render the analytics dashboard. */ public function render_dashboard() { if ( ! current_user_can( 'manage_options' ) ) { return; } $range = $this->current_range(); $totals = Aiso_Store::totals( $range ); $connected = Aiso_Analytics::instance()->sync->is_connected(); echo '
' . esc_html__( 'Impressions for the AI era — see exactly which AI assistants are reading your site, what they read, and when.', 'aiso-ai-crawler-analytics' ) . '
'; echo '' . esc_html__( 'As soon as ChatGPT, Claude, Perplexity, Google or another AI crawler requests a page on this site, it will appear here. AI traffic is usually a small slice of total traffic, so it can take a little while.', 'aiso-ai-crawler-analytics' ) . '
'; echo '' . esc_html__( 'Want to test it now? From a terminal, run:', 'aiso-ai-crawler-analytics' ) . '
'; echo 'curl -A "Mozilla/5.0 (compatible; GPTBot/1.1; +https://openai.com/gptbot)" ' . esc_html( home_url( '/' ) ) . ""; echo '
' . esc_html__( 'Reload this page after a few seconds and the hit will show up.', 'aiso-ai-crawler-analytics' ) . '
'; if ( ! $connected ) { echo '' . esc_html__( 'Connect your Aiso account', 'aiso-ai-crawler-analytics' ) . '
'; } echo '' . esc_html__( 'Which assistants are reading your content.', 'aiso-ai-crawler-analytics' ) . '
'; foreach ( $rows as $r ) { $key = $r['label']; $label = isset( $platforms[ $key ]['label'] ) ? $platforms[ $key ]['label'] : ucfirst( $key ); $color = isset( $platforms[ $key ]['color'] ) ? $platforms[ $key ]['color'] : '#6d5ae8'; $this->bar( $label, (int) $r['count'], $max, $total, $color ); } echo '' . esc_html__( 'Training vs. citation vs. live chat impressions.', 'aiso-ai-crawler-analytics' ) . '
'; foreach ( $rows as $r ) { $key = $r['label']; $label = isset( $fns[ $key ]['label'] ) ? $fns[ $key ]['label'] : ucfirst( str_replace( '-', ' ', $key ) ); $color = isset( $fns[ $key ]['color'] ) ? $fns[ $key ]['color'] : '#6d5ae8'; $this->bar( $label, (int) $r['count'], $max, $total, $color ); } echo '' . esc_html__( 'The exact bots hitting your server.', 'aiso-ai-crawler-analytics' ) . '
'; foreach ( $rows as $r ) { $slug = $r['label']; $name = isset( $meta[ $slug ]['name'] ) ? $meta[ $slug ]['name'] : ( 'other' === $slug ? __( 'Other / Unknown', 'aiso-ai-crawler-analytics' ) : $slug ); $pkey = isset( $meta[ $slug ]['platform'] ) ? $meta[ $slug ]['platform'] : 'other'; $color = isset( $platforms[ $pkey ]['color'] ) ? $platforms[ $pkey ]['color'] : '#6d5ae8'; $this->bar( $name, (int) $r['count'], $max, $total, $color ); } echo '' . esc_html__( 'The URLs AI crawlers request most.', 'aiso-ai-crawler-analytics' ) . '
'; echo '| ' . esc_html__( 'Page', 'aiso-ai-crawler-analytics' ) . ' | '; echo '' . esc_html__( 'Hits', 'aiso-ai-crawler-analytics' ) . ' | '; echo '' . esc_html__( 'Impr.', 'aiso-ai-crawler-analytics' ) . ' | '; echo '' . esc_html__( 'Bots', 'aiso-ai-crawler-analytics' ) . ' | '; echo '
|---|---|---|---|
| ' . esc_html( $r['path'] ) . ' | '; echo '' . esc_html( number_format_i18n( (int) $r['hits'] ) ) . ' | '; echo '' . esc_html( number_format_i18n( (int) $r['impressions'] ) ) . ' | '; echo '' . esc_html( number_format_i18n( (int) $r['bots'] ) ) . ' | '; echo '
' . esc_html__( 'Daily AI crawler hits. Green marks live chat impressions.', 'aiso-ai-crawler-analytics' ) . '
'; echo '| ' . esc_html__( 'Time (UTC)', 'aiso-ai-crawler-analytics' ) . ' | '; echo '' . esc_html__( 'Crawler', 'aiso-ai-crawler-analytics' ) . ' | '; echo '' . esc_html__( 'Purpose', 'aiso-ai-crawler-analytics' ) . ' | '; echo '' . esc_html__( 'Page', 'aiso-ai-crawler-analytics' ) . ' | '; echo '' . esc_html__( 'Country', 'aiso-ai-crawler-analytics' ) . ' | '; echo '' . esc_html__( 'Verified', 'aiso-ai-crawler-analytics' ) . ' | '; echo '||
|---|---|---|---|---|---|---|---|
| ' . esc_html( $r['hit_time'] ) . ' | '; echo '' . esc_html( $r['bot_name'] ) . ' | '; echo '' . esc_html( $fnlbl ) . ' | '; echo '' . esc_html( $r['path'] ) . ' | '; echo '' . esc_html( $r['country'] ) . ' | '; if ( null === $r['verified'] ) { echo '' . esc_html__( '—', 'aiso-ai-crawler-analytics' ) . ' | '; } elseif ( (int) $r['verified'] === 1 ) { echo '' . esc_html__( 'verified', 'aiso-ai-crawler-analytics' ) . ' | '; } else { echo '' . esc_html__( 'spoofed?', 'aiso-ai-crawler-analytics' ) . ' | '; } echo '
' . esc_html__( 'Verification compares the source IP against each provider’s published ranges. “—” means the provider publishes no list (verify by reverse-DNS).', 'aiso-ai-crawler-analytics' ) . '
'; echo '' . esc_html__( 'Connecting sends AI-crawler hits to your Aiso dashboard so data is kept long-term and viewable across all your sites. Until you connect, the plugin works fully on its own and stores everything locally — nothing leaves your server.', 'aiso-ai-crawler-analytics' ) . '
'; if ( $connected ) { echo '' . esc_html__( 'Status:', 'aiso-ai-crawler-analytics' ) . ' ' . esc_html__( 'Connected & syncing', 'aiso-ai-crawler-analytics' ) . '
'; } else { echo '' . esc_html__( 'Status:', 'aiso-ai-crawler-analytics' ) . ' ' . esc_html__( 'Local only', 'aiso-ai-crawler-analytics' ) . '
'; // Generate-key form. echo ''; } echo ''; printf( /* translators: %s: number of pending hits. */ esc_html( _n( '%s hit waiting to sync.', '%s hits waiting to sync.', $pending, 'aiso-ai-crawler-analytics' ) ), esc_html( number_format_i18n( $pending ) ) ); if ( $last_sync ) { echo ' ' . esc_html( sprintf( /* translators: %s: human-readable time difference. */ __( 'Last sync %s ago.', 'aiso-ai-crawler-analytics' ), human_time_diff( $last_sync, time() ) ) ); } echo '
'; // Test + Sync buttons. echo '' . esc_html__( 'Only when “Send data to Aiso” is enabled, and only for AI-crawler hits, this plugin transmits: the requested path, the crawler’s user-agent, the detected bot/company, an (optionally hashed) IP, country code, HTTP status, timestamp, your site URL, and your API key. No human-visitor analytics or personal content is sent.', 'aiso-ai-crawler-analytics' ) . '
'; echo '' . esc_html__( 'Aiso privacy policy', 'aiso-ai-crawler-analytics' ) . ' · ' . esc_html__( 'About Aiso', 'aiso-ai-crawler-analytics' ) . '
'; echo '