' . 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 '
';
foreach ( $tabs as $key => $label ) {
printf(
'%s ',
esc_url( self::url( $key ) ),
esc_attr( $key === $tab ? ' is-active' : '' ),
esc_html( $label )
);
}
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 '';
foreach ( $daily as $date => $n ) {
printf(
' ',
(int) max( 2, round( 100 * $n / $max ) ),
esc_attr( sprintf( '%s: %d', date_i18n( get_option( 'date_format' ), strtotime( $date ) ), $n ) )
);
}
echo '
';
echo '';
echo '
' . esc_html__( 'Visits by bot (30 days)', 'agentaisle' ) . ' ';
if ( $counts ) {
echo '' . esc_html__( 'Bot', 'agentaisle' ) . ' ' . esc_html__( 'Type', 'agentaisle' ) . ' ' . esc_html__( 'Visits', 'agentaisle' ) . ' ';
foreach ( $counts as $row ) {
$info = $bots[ $row->bot ] ?? array( 'operator' => '', 'type' => '' );
printf(
'%s %s %s %s ',
esc_html( $row->bot ),
esc_html( $info['operator'] ),
esc_html( AgentAisle_Bots::type_label( $info['type'] ) ),
esc_html( number_format_i18n( $row->visits ) )
);
}
echo '
';
} 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(
'%s%s %s ',
esc_html( $label ? $label : $row->path ),
esc_html( $row->path ),
esc_html( number_format_i18n( $row->visits ) )
);
}
echo '
';
} 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 '' . 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' ) . ' ';
foreach ( AgentAisle_Bots::all() as $token => $info ) {
$row = $counts[ $token ] ?? null;
printf(
'%4$s %5$s %6$s %7$s %8$s ',
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 '
';
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(
' %s
',
checked( ! empty( $settings['llms_enabled'] ), true, false ),
esc_html__( 'Publish llms.txt', 'agentaisle' )
);
echo '' . esc_html__( 'Content (leave empty to use the automatic version)', 'agentaisle' ) . ' ';
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' ) . ' ';
foreach ( AgentAisle_Readiness::store_checks() as $check ) {
printf(
'%s %s%s ',
esc_attr( $check['pass'] ? 'is-pass' : 'is-fail' ),
esc_html( $check['label'] ),
wp_kses_post( $check['pass'] ? '' : '' . esc_html( $check['help'] ) . ' ' ),
$check['pass'] ? esc_html__( 'Passed', 'agentaisle' ) : esc_html__( 'Needs attention', 'agentaisle' )
);
}
echo ' ';
$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 '' . esc_html__( 'Product', 'agentaisle' ) . ' ' . esc_html__( 'Score', 'agentaisle' ) . ' ' . esc_html__( 'Missing', 'agentaisle' ) . ' ';
foreach ( $rows as $row ) {
$class = $row['score'] >= 80 ? 'good' : ( $row['score'] >= 50 ? 'ok' : 'poor' );
printf(
'%s %d%% %s ',
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 '
';
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 '';
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 '