' . esc_html__( 'Dashboard', 'agentaisle' ) . '' ); return $links; } /** * Enqueue CSS on our screen only. * * @param string $hook Hook suffix. */ public static function assets( $hook ) { if ( false === strpos( $hook, self::SLUG ) ) { return; } wp_enqueue_style( 'agentaisle-admin', AGENTAISLE_URL . 'assets/admin.css', array(), AGENTAISLE_VERSION ); } /** * Admin URL for a tab. * * @param string $tab Tab. * @return string */ public static function url( $tab = 'dashboard' ) { return add_query_arg( array( 'page' => self::SLUG, 'tab' => $tab, ), admin_url( 'admin.php' ) ); } /** * Tabs. * * @return array */ private static function tabs() { $tabs = array( 'dashboard' => __( 'Dashboard', 'agentaisle' ), 'crawlers' => __( 'AI crawlers', 'agentaisle' ), 'llms' => __( 'llms.txt', 'agentaisle' ), 'readiness' => __( 'Readiness', 'agentaisle' ), 'settings' => __( 'Settings', 'agentaisle' ), ); /** * Filter the admin tabs. Add-ons render their own tabs on * the agentaisle_render_tab_{$tab} action. * * @param array $tabs Tab key => label. */ return apply_filters( 'agentaisle_admin_tabs', $tabs ); } /** * Page wrapper. */ public static function render() { if ( ! current_user_can( self::CAP ) ) { return; } $tabs = self::tabs(); $tab = sanitize_key( (string) filter_input( INPUT_GET, 'tab', FILTER_UNSAFE_RAW ) ); if ( ! isset( $tabs[ $tab ] ) ) { $tab = 'dashboard'; } echo '
'; echo '

' . esc_html( apply_filters( 'agentaisle_admin_title', __( 'AgentAisle', 'agentaisle' ) ) ) . '

'; if ( '1' === filter_input( INPUT_GET, 'saved', FILTER_UNSAFE_RAW ) ) { echo '

' . esc_html__( 'Settings saved.', 'agentaisle' ) . '

'; } echo '
'; if ( has_action( 'agentaisle_render_tab_' . $tab ) ) { do_action( 'agentaisle_render_tab_' . $tab ); } elseif ( method_exists( __CLASS__, 'tab_' . $tab ) ) { call_user_func( array( __CLASS__, 'tab_' . $tab ) ); } echo '
'; } /** * Dashboard tab. */ private static function tab_dashboard() { $settings = agentaisle_settings(); $counts = AgentAisle_Logger::counts_by_bot( 30 ); $daily = AgentAisle_Logger::daily( 30 ); $paths = AgentAisle_Logger::top_paths( 30, 10 ); $total = array_sum( $daily ); $week = array_sum( array_slice( $daily, -7 ) ); $report = AgentAisle_Readiness::product_report(); $avg = AgentAisle_Readiness::average( $report ); $bots = AgentAisle_Bots::all(); if ( empty( $settings['logging'] ) ) { echo '

' . esc_html__( 'Visit logging is switched off in Settings.', 'agentaisle' ) . '

'; } echo '
'; self::stat( number_format_i18n( $week ), __( 'AI visits, last 7 days', 'agentaisle' ) ); self::stat( number_format_i18n( $total ), __( 'AI visits, last 30 days', 'agentaisle' ) ); self::stat( number_format_i18n( count( $counts ) ), __( 'Distinct AI bots', 'agentaisle' ) ); self::stat( null === $avg ? '–' : $avg . '%', __( 'Product readiness', 'agentaisle' ) ); echo '
'; echo '

' . esc_html__( 'Daily AI visits', 'agentaisle' ) . '

'; $max = max( 1, max( $daily ) ); echo '
'; echo '
'; echo '

' . esc_html__( 'Visits by bot (30 days)', 'agentaisle' ) . '

'; if ( $counts ) { echo ''; foreach ( $counts as $row ) { $info = $bots[ $row->bot ] ?? array( 'operator' => '', 'type' => '' ); printf( '', esc_html( $row->bot ), esc_html( $info['operator'] ), esc_html( AgentAisle_Bots::type_label( $info['type'] ) ), esc_html( number_format_i18n( $row->visits ) ) ); } echo '
' . esc_html__( 'Bot', 'agentaisle' ) . '' . esc_html__( 'Type', 'agentaisle' ) . '' . esc_html__( 'Visits', 'agentaisle' ) . '
%s
%s
%s%s
'; } else { self::empty_state(); } echo '
'; echo '

' . esc_html__( 'Most-crawled pages (30 days)', 'agentaisle' ) . '

'; if ( $paths ) { echo ''; foreach ( $paths as $row ) { $label = $row->object_id ? get_the_title( (int) $row->object_id ) : ''; printf( '', esc_html( $label ? $label : $row->path ), esc_html( $row->path ), esc_html( number_format_i18n( $row->visits ) ) ); } echo '
%s
%s
%s
'; } else { self::empty_state(); } echo '
'; if ( self::show_upsell() ) { echo '

' . esc_html__( 'Want to see sales that came from AI assistants, full history and your whole catalogue scored?', 'agentaisle' ) . ' ' . esc_html__( 'See AgentAisle Pro', 'agentaisle' ) . '

'; } } /** * Crawlers tab. */ private static function tab_crawlers() { $settings = agentaisle_settings(); $blocked = (array) $settings['blocked_bots']; $counts = array(); foreach ( AgentAisle_Logger::counts_by_bot( 30 ) as $row ) { $counts[ $row->bot ] = $row; } echo '

' . esc_html__( 'Choose which AI bots may crawl your store. Blocking adds a rule to your robots.txt. Reputable bots follow it, but it is a request, not a guarantee.', 'agentaisle' ) . '

'; echo '

' . esc_html__( 'Training bots collect content for future AI models. AI search bots power answers that can link to your products. User agents fetch pages when a person asks an assistant about you; blocking these can stop your products being recommended.', 'agentaisle' ) . '

'; if ( AgentAisle_Robots::physical_file_exists() ) { echo '

' . esc_html__( 'A physical robots.txt file exists in your site root, so WordPress cannot add these rules. Remove or rename that file, or copy the rules into it manually.', 'agentaisle' ) . '

'; } self::form_open( 'crawlers' ); echo ''; foreach ( AgentAisle_Bots::all() as $token => $info ) { $row = $counts[ $token ] ?? null; printf( '', esc_attr( $token ), esc_attr( sanitize_html_class( $token ) ), checked( ! in_array( $token, $blocked, true ), true, false ), esc_html( $token ), esc_html( $info['operator'] ), esc_html( AgentAisle_Bots::type_label( $info['type'] ) ), esc_html( $row ? number_format_i18n( $row->visits ) : '0' ), esc_html( $row ? human_time_diff( strtotime( $row->last_seen . ' UTC' ) ) . ' ' . __( 'ago', 'agentaisle' ) : '–' ) ); } echo '
' . esc_html__( 'Allow', 'agentaisle' ) . '' . esc_html__( 'Bot', 'agentaisle' ) . '' . esc_html__( 'Operator', 'agentaisle' ) . '' . esc_html__( 'Type', 'agentaisle' ) . '' . esc_html__( 'Visits (30d)', 'agentaisle' ) . '' . esc_html__( 'Last seen', 'agentaisle' ) . '
%5$s%6$s%7$s%8$s
'; submit_button( __( 'Save crawler rules', 'agentaisle' ) ); echo ''; printf( '

%s %s

', esc_html__( 'Check the result:', 'agentaisle' ), esc_url( home_url( '/robots.txt' ) ), esc_html__( 'view robots.txt', 'agentaisle' ) ); } /** * llms.txt tab. */ private static function tab_llms() { $settings = agentaisle_settings(); echo '

' . esc_html__( 'llms.txt gives AI assistants a clean summary of your store: key pages, categories and popular products. AgentAisle builds it automatically; you can edit it if you prefer.', 'agentaisle' ) . '

'; self::form_open( 'llms' ); printf( '

', checked( ! empty( $settings['llms_enabled'] ), true, false ), esc_html__( 'Publish llms.txt', 'agentaisle' ) ); echo '

'; printf( '

', esc_attr( AgentAisle_Llms::generate() ), esc_textarea( (string) $settings['llms_custom'] ) ); submit_button( __( 'Save llms.txt', 'agentaisle' ) ); echo ''; printf( '

%s · %s

', esc_url( home_url( '/llms.txt' ) ), esc_html__( 'View live llms.txt', 'agentaisle' ), esc_html__( 'If it shows a 404, visit Settings > Permalinks and click Save once.', 'agentaisle' ) ); } /** * Readiness tab. */ private static function tab_readiness() { echo '

' . esc_html__( 'Store checks', 'agentaisle' ) . '

'; $rows = AgentAisle_Readiness::product_report(); echo '

' . esc_html__( 'Product readiness', 'agentaisle' ) . '

'; /* translators: %d: number of products */ echo '

' . esc_html( sprintf( __( 'Your %d most recent products, weakest first. AI assistants favour products with complete, accurate data.', 'agentaisle' ), AgentAisle_Readiness::limit() ) ) . '

'; if ( ! $rows ) { echo '

' . esc_html__( 'No published products found.', 'agentaisle' ) . '

'; return; } echo ''; foreach ( $rows as $row ) { $class = $row['score'] >= 80 ? 'good' : ( $row['score'] >= 50 ? 'ok' : 'poor' ); printf( '', esc_url( (string) $row['edit'] ), esc_html( $row['name'] ), esc_attr( $class ), (int) $row['score'], wp_kses_post( $row['missing'] ? esc_html( implode( ', ', $row['missing'] ) ) : '' . esc_html__( 'Nothing', 'agentaisle' ) . '' ) ); } echo '
' . esc_html__( 'Product', 'agentaisle' ) . '' . esc_html__( 'Score', 'agentaisle' ) . '' . esc_html__( 'Missing', 'agentaisle' ) . '
%s%d%%%s
'; if ( self::show_upsell() ) { echo '

' . esc_html__( 'Score your whole catalogue and fix missing brands and GTINs in bulk with', 'agentaisle' ) . ' ' . esc_html__( 'AgentAisle Pro', 'agentaisle' ) . '.

'; } } /** * Settings tab. */ private static function tab_settings() { $settings = agentaisle_settings(); self::form_open( 'settings' ); echo ''; printf( '', esc_html__( 'Visit logging', 'agentaisle' ), checked( ! empty( $settings['logging'] ), true, false ), esc_html__( 'Log visits from known AI bots', 'agentaisle' ), esc_html__( 'Only bot visits are logged. IP addresses are stored as a short one-way hash, never in full. Data stays on your server.', 'agentaisle' ) ); printf( '', esc_html__( 'Keep logs for', 'agentaisle' ), (int) agentaisle_max_retention_days(), (int) $settings['retention_days'], esc_html__( 'days', 'agentaisle' ), /* translators: %d: maximum number of days */ esc_html( sprintf( __( 'Up to %d days. Older entries are deleted daily.', 'agentaisle' ), agentaisle_max_retention_days() ) ) ); echo ''; submit_button(); echo ''; } /** * Save handler. */ public static function save() { if ( ! current_user_can( self::CAP ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'agentaisle' ) ); } check_admin_referer( 'agentaisle_save' ); $section = isset( $_POST['section'] ) ? sanitize_key( wp_unslash( $_POST['section'] ) ) : ''; $settings = agentaisle_settings(); switch ( $section ) { case 'crawlers': $allowed = isset( $_POST['allowed'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['allowed'] ) ) : array(); $settings['blocked_bots'] = array_values( array_diff( array_keys( AgentAisle_Bots::all() ), $allowed ) ); break; case 'llms': $settings['llms_enabled'] = empty( $_POST['llms_enabled'] ) ? 0 : 1; $settings['llms_custom'] = isset( $_POST['llms_custom'] ) ? sanitize_textarea_field( wp_unslash( $_POST['llms_custom'] ) ) : ''; break; case 'settings': $settings['logging'] = empty( $_POST['logging'] ) ? 0 : 1; $settings['retention_days'] = isset( $_POST['retention_days'] ) ? min( agentaisle_max_retention_days(), max( 1, absint( $_POST['retention_days'] ) ) ) : 30; break; default: wp_safe_redirect( self::url() ); exit; } update_option( 'agentaisle_settings', $settings, false ); wp_safe_redirect( add_query_arg( 'saved', 1, self::url( $section ) ) ); exit; } /** * Open a settings form. * * @param string $section Section. */ private static function form_open( $section ) { echo '
'; echo ''; echo ''; wp_nonce_field( 'agentaisle_save' ); } /** * Whether to show the Pro links. Add-ons switch this off. * * @return bool */ private static function show_upsell() { return (bool) apply_filters( 'agentaisle_show_upsell', true ); } /** * Stat tile. * * @param string $value Value. * @param string $label Label. */ private static function stat( $value, $label ) { printf( '
%s%s
', esc_html( $value ), esc_html( $label ) ); } /** * Empty state. */ private static function empty_state() { echo '

' . esc_html__( 'No AI bot visits recorded yet. Most stores see their first within a few days.', 'agentaisle' ) . '

'; } }