human label. */ private const SOURCES = [ 'ai2web' => 'Agent checkout', 'ai2web-acp' => 'ACP (ChatGPT)', 'ai2web-ap2' => 'AP2 (Google)', ]; public static function boot(): void { add_action('admin_menu', [self::class, 'menu']); } public static function menu(): void { add_submenu_page( 'options-general.php', __('AI2Web Agent Sales', 'ai2web'), __('AI2Web Agent Sales', 'ai2web'), 'manage_woocommerce', 'ai2web-sales', [self::class, 'render'] ); } public static function render(): void { if (!current_user_can('manage_woocommerce')) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only range selector. $range = isset($_GET['range']) ? absint($_GET['range']) : 30; $days = in_array($range, [7, 30, 90, 365], true) ? $range : 30; echo '
'; echo '

' . esc_html__('AI2Web Agent Sales', 'ai2web') . '

'; if (!class_exists('WooCommerce')) { echo '

' . esc_html__('WooCommerce is not active, so there are no agent orders to show.', 'ai2web') . '

'; return; } // Range selector. echo '

'; foreach ([7 => __('7 days', 'ai2web'), 30 => __('30 days', 'ai2web'), 90 => __('90 days', 'ai2web'), 365 => __('12 months', 'ai2web')] as $d => $label) { $url = admin_url('options-general.php?page=ai2web-sales&range=' . $d); $cur = $d === $days; echo '' . esc_html($label) . ''; if ($d !== 365) { echo '  |  '; } } echo '

'; $orders = self::agent_orders($days); $stats = self::summarise($orders); // KPI cards. echo '
'; self::kpi(__('Agent revenue', 'ai2web'), wc_price($stats['paid_revenue']), sprintf( /* translators: %d: number of days */ __('paid, last %d days', 'ai2web'), $days )); self::kpi(__('Agent orders', 'ai2web'), (string) $stats['count'], sprintf( /* translators: %d: number of paid orders */ __('%d paid', 'ai2web'), $stats['paid_count'] )); self::kpi(__('Avg order value', 'ai2web'), $stats['paid_count'] ? wc_price($stats['paid_revenue'] / $stats['paid_count']) : '-', __('paid orders', 'ai2web')); self::kpi(__('Pending value', 'ai2web'), wc_price($stats['pending_revenue']), __('awaiting payment', 'ai2web')); echo '
'; // By protocol. echo '

' . esc_html__('By protocol', 'ai2web') . '

'; echo ''; echo ''; if (empty($stats['by_source'])) { echo ''; } foreach ($stats['by_source'] as $via => $row) { echo ''; } echo '
' . esc_html__('Source', 'ai2web') . '' . esc_html__('Orders', 'ai2web') . '' . esc_html__('Paid', 'ai2web') . '' . esc_html__('Revenue (paid)', 'ai2web') . '
' . esc_html__('No agent orders in this period yet.', 'ai2web') . '
' . esc_html(self::SOURCES[$via] ?? $via) . '' . esc_html((string) $row['count']) . '' . esc_html((string) $row['paid_count']) . '' . wp_kses_post(wc_price($row['paid_revenue'])) . '
'; // Recent agent orders. echo '

' . esc_html__('Recent agent orders', 'ai2web') . '

'; echo ''; echo ''; $recent = array_slice($orders, 0, 20); if (empty($recent)) { echo ''; } foreach ($recent as $o) { $edit = $o->get_edit_order_url(); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__('Order', 'ai2web') . '' . esc_html__('Date', 'ai2web') . '' . esc_html__('Source', 'ai2web') . '' . esc_html__('Status', 'ai2web') . '' . esc_html__('Total', 'ai2web') . '
' . esc_html__('No agent orders yet. When an AI agent buys through ACP, AP2 or agent checkout, it appears here.', 'ai2web') . '
#' . esc_html($o->get_order_number()) . '' . esc_html($o->get_date_created() ? $o->get_date_created()->date_i18n(get_option('date_format') . ' H:i') : '-') . '' . esc_html(self::SOURCES[$o->get_created_via()] ?? $o->get_created_via()) . '' . esc_html(wc_get_order_status_name($o->get_status())) . '' . wp_kses_post(wc_price($o->get_total(), ['currency' => $o->get_currency()])) . '
'; // Engagement (events table). self::render_engagement($days); echo '

' . esc_html__('All figures are computed locally from your WooCommerce orders and the AI2Web events table. Nothing is sent to any external service.', 'ai2web') . '

'; echo ''; } /** * Agent-attributed orders in the window, newest first (drafts excluded). * @return WC_Order[] */ private static function agent_orders(int $days): array { if (!function_exists('wc_get_orders')) { return []; } $orders = wc_get_orders([ 'limit' => -1, 'type' => 'shop_order', 'date_created' => '>=' . gmdate('Y-m-d', time() - $days * DAY_IN_SECONDS), 'orderby' => 'date', 'order' => 'DESC', ]); $out = []; foreach ($orders as $o) { // Filter by created_via in PHP so this is correct whether or not the query backend // supports created_via as a filter, and across HPOS / legacy storage. if ($o instanceof WC_Order && isset(self::SOURCES[$o->get_created_via()]) && $o->get_status() !== 'checkout-draft') { $out[] = $o; } } return $out; } /** * @param WC_Order[] $orders * @return array{count:int,paid_count:int,paid_revenue:float,pending_revenue:float,by_source:array} */ private static function summarise(array $orders): array { $s = ['count' => 0, 'paid_count' => 0, 'paid_revenue' => 0.0, 'pending_revenue' => 0.0, 'by_source' => []]; foreach ($orders as $o) { $via = $o->get_created_via(); $total = (float) $o->get_total(); $paid = $o->is_paid(); $s['count']++; $s['by_source'][$via] ??= ['count' => 0, 'paid_count' => 0, 'paid_revenue' => 0.0]; $s['by_source'][$via]['count']++; if ($paid) { $s['paid_count']++; $s['paid_revenue'] += $total; $s['by_source'][$via]['paid_count']++; $s['by_source'][$via]['paid_revenue'] += $total; } else { $s['pending_revenue'] += $total; } } return $s; } private static function render_engagement(int $days): void { global $wpdb; $table = $wpdb->prefix . 'ai2web_events'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) !== $table) { return; } $cutoff = gmdate('Y-m-d H:i:s', time() - $days * DAY_IN_SECONDS); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $table is $wpdb->prefix . a constant table name (not user input); the value is prepared. $rows = $wpdb->get_results($wpdb->prepare("SELECT type, result, COUNT(*) c FROM {$table} WHERE ts >= %s GROUP BY type, result", $cutoff), ARRAY_A); if (empty($rows)) { return; } $discovery = 0; $queries = 0; $misses = 0; $actions = 0; foreach ($rows as $r) { $c = (int) $r['c']; if ($r['type'] === 'discovery') { $discovery += $c; } elseif ($r['type'] === 'query') { $queries += $c; if ($r['result'] === 'miss') { $misses += $c; } } elseif ($r['type'] === 'action') { $actions += $c; } } echo '

' . esc_html__('Agent engagement', 'ai2web') . '

'; echo '
'; self::kpi(__('Discovery hits', 'ai2web'), (string) $discovery, __('manifest fetched', 'ai2web')); self::kpi(__('Queries', 'ai2web'), (string) $queries, __('content / product / search', 'ai2web')); self::kpi(__('Query misses', 'ai2web'), (string) $misses, __('unmet demand', 'ai2web')); self::kpi(__('Actions', 'ai2web'), (string) $actions, __('agent action calls', 'ai2web')); echo '
'; if ($misses > 0) { echo '

' . esc_html__('Query misses are searches an agent ran that returned nothing - demand you are not yet meeting. A read-only crawl of your site cannot surface this.', 'ai2web') . '

'; } } private static function kpi(string $label, string $value, string $sub): void { echo '
'; echo '
' . esc_html($label) . '
'; echo '
' . wp_kses_post($value) . '
'; echo '
' . esc_html($sub) . '
'; echo '
'; } }