Agent Stamp: Agents, Readiness, Orders, Rules. * * The Agents tab shows every agent visit the store keeps (90 days): the last 7, 30 or 90 days. * * @package AgentStamp */ if ( ! defined( 'ABSPATH' ) ) { exit; } class AgentStamp_Admin { public static function init() { add_action( 'admin_post_agent_stamp_rules', array( __CLASS__, 'save_rules' ) ); add_action( 'admin_post_agent_stamp_fix', array( __CLASS__, 'apply_fix' ) ); } /** * @return string */ private static function current_tab() { // Read-only view choice, checked against a fixed list. $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( '' === $tab && isset( $_GET['period'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = 'orders'; } return in_array( $tab, array( 'agents', 'readiness', 'orders', 'rules' ), true ) ? $tab : 'agents'; } /** * @param string $tab * @param array $args * @return string */ public static function url( $tab, array $args = array() ) { return add_query_arg( array_merge( array( 'page' => 'agent-stamp', 'tab' => $tab ), $args ), admin_url( 'admin.php' ) ); } /** * @return string The current tab. */ public static function render_tabs() { $current = self::current_tab(); $tabs = array( 'agents' => __( 'Agents', 'agent-stamp' ), 'readiness' => __( 'Readiness', 'agent-stamp' ), 'orders' => __( 'Orders', 'agent-stamp' ), 'rules' => __( 'Rules', 'agent-stamp' ), ); echo ''; // Result of a one-click fix or a save. if ( isset( $_GET['agent_stamp_done'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended echo '

' . esc_html__( 'Saved.', 'agent-stamp' ) . '

'; } return $current; } /** * Views the Agents tab offers. The last one is every visit the store keeps. * * @return int[] */ private static function funnel_periods() { return array( 7, 30, AgentStamp_Events::keep_days() ); } /** * @return int */ private static function funnel_days() { $allowed = self::funnel_periods(); $days = isset( $_GET['days'] ) ? absint( wp_unslash( $_GET['days'] ) ) : $allowed[0]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended return in_array( $days, $allowed, true ) ? $days : $allowed[0]; } /** * Prints where a free install reads about the separate Pro plugin. */ public static function pro_link() { echo '' . esc_html__( 'About Agent Stamp Pro', 'agent-stamp' ) . ''; } /** * @param float $amount * @return string */ private static function money( $amount ) { return function_exists( 'wc_price' ) ? wp_strip_all_tags( wc_price( $amount ) ) : number_format( (float) $amount, 2 ); } public static function render_agents() { $days = self::funnel_days(); $funnel = AgentStamp_Sensor::summary( $days ); echo '

'; foreach ( self::funnel_periods() as $index => $period ) { $label = sprintf( /* translators: %d: days */ __( 'Last %d days', 'agent-stamp' ), $period ); if ( $index > 0 ) { echo ' · '; } if ( $period === $days ) { echo '' . esc_html( $label ) . ''; } else { echo '' . esc_html( $label ) . ''; } } echo '

'; if ( 0 === $funnel['sessions'] ) { echo '

' . esc_html__( 'No AI agent has visited since you installed', 'agent-stamp' ) . '

'; echo '

' . esc_html__( 'That is normal at first. Agent visits show here as they happen, from the first page an agent opens to the order, with the reason when your store stops one. A person\'s visit is not recorded.', 'agent-stamp' ) . '

'; echo '

' . esc_html__( 'Fix what would stop an agent before the first one arrives.', 'agent-stamp' ) . ' ' . esc_html__( 'Open Readiness', 'agent-stamp' ) . '

'; self::render_blind_spots(); return; } $visited = sprintf( /* translators: %d: visits */ _n( 'AI agents visited %d time', 'AI agents visited %d times', $funnel['sessions'], 'agent-stamp' ), $funnel['sessions'] ); $paid = sprintf( /* translators: 1: orders, 2: revenue */ _n( 'and paid for %1$d order (%2$s).', 'and paid for %1$d orders (%2$s).', $funnel['orders'], 'agent-stamp' ), $funnel['orders'], self::money( $funnel['revenue'] ) ); echo '

' . esc_html( $visited . ' ' . $paid ) . '

'; if ( $funnel['failed'] > 0 ) { echo '

' . esc_html( sprintf( /* translators: 1: visits, 2: cart value */ _n( '%1$d visit hit an error from your store. Carts stopped that way: %2$s.', '%1$d visits hit an error from your store. Carts stopped that way: %2$s.', $funnel['failed'], 'agent-stamp' ), $funnel['failed'], self::money( $funnel['lost_value'] ) ) ) . '

'; } if ( $funnel['scripts'] > 0 ) { echo '

' . esc_html( sprintf( /* translators: 1: script visits, 2: failed ones */ _n( 'Scripts also reached the cart %1$d time, %2$d with an error. They are not shoppers, so they are not counted above. Card testers look like this: see By agent.', 'Scripts also reached the cart %1$d times, %2$d with an error. They are not shoppers, so they are not counted above. Card testers look like this: see By agent.', $funnel['scripts'], 'agent-stamp' ), $funnel['scripts'], $funnel['script_failures'] ) ) . '

'; } $steps = array( __( 'Visited', 'agent-stamp' ) => $funnel['sessions'], __( 'Opened a product', 'agent-stamp' ) => $funnel['product'], __( 'Built a cart', 'agent-stamp' ) => $funnel['cart'], __( 'Reached checkout', 'agent-stamp' ) => $funnel['checkout'], __( 'Visits that paid', 'agent-stamp' ) => $funnel['ordered'], ); echo ''; foreach ( $steps as $label => $count ) { $width = $funnel['sessions'] > 0 ? (int) round( 100 * $count / $funnel['sessions'] ) : 0; echo ''; } echo '
' . esc_html( $label ) . '' . esc_html( (string) $count ) . '
'; echo '

' . esc_html__( 'Why agents did not buy', 'agent-stamp' ) . '

'; if ( empty( $funnel['reasons'] ) ) { echo '

' . esc_html__( 'No failures recorded.', 'agent-stamp' ) . '

'; } else { echo ''; foreach ( $funnel['reasons'] as $reason ) { $explain = AgentStamp_Funnel::explain( $reason['reason'] ); echo ''; } echo '
' . esc_html__( 'Reason', 'agent-stamp' ) . '' . esc_html__( 'Visits', 'agent-stamp' ) . '' . esc_html__( 'Cart value', 'agent-stamp' ) . '' . esc_html__( 'What to do', 'agent-stamp' ) . '
' . esc_html( $explain['title'] ) . ''; if ( '' !== $reason['message'] ) { echo '
' . esc_html( $reason['message'] ) . ''; } echo '
' . esc_html( (string) $reason['sessions'] ) . '' . esc_html( 'no_error' === $reason['reason'] ? '—' : self::money( $reason['value'] ) ) . '' . esc_html( $explain['fix'] ) . '
'; } echo '

' . esc_html__( 'By agent', 'agent-stamp' ) . '

'; echo ''; foreach ( $funnel['agents'] as $agent ) { echo ''; } echo '
' . esc_html__( 'Agent', 'agent-stamp' ) . '' . esc_html__( 'Visits', 'agent-stamp' ) . '' . esc_html__( 'Cart', 'agent-stamp' ) . '' . esc_html__( 'Checkout', 'agent-stamp' ) . '' . esc_html__( 'Paid', 'agent-stamp' ) . '' . esc_html__( 'Revenue', 'agent-stamp' ) . '' . esc_html__( 'Store errors', 'agent-stamp' ) . '
' . esc_html( $agent['agent'] ) . '' . esc_html( (string) $agent['sessions'] ) . '' . esc_html( (string) $agent['cart'] ) . '' . esc_html( (string) $agent['checkout'] ) . '' . esc_html( (string) $agent['ordered'] ) . '' . esc_html( self::money( $agent['revenue'] ) ) . '' . esc_html( (string) $agent['failed'] ) . '
'; $recent = AgentStamp_Events::failures( time() - $days * DAY_IN_SECONDS, 25 ); if ( ! empty( $recent ) ) { echo '

' . esc_html__( 'Latest failures', 'agent-stamp' ) . '

'; echo ''; foreach ( $recent as $row ) { $explain = AgentStamp_Funnel::explain( $row['reason'] ); echo ''; } echo '
' . esc_html__( 'When', 'agent-stamp' ) . '' . esc_html__( 'Agent', 'agent-stamp' ) . '' . esc_html__( 'Step', 'agent-stamp' ) . '' . esc_html__( 'Reason', 'agent-stamp' ) . '' . esc_html__( 'What the store said', 'agent-stamp' ) . '
' . esc_html( wp_date( 'M j H:i', (int) $row['created_at'] ) ) . '' . esc_html( $row['agent'] ) . '' . esc_html( $row['stage'] ) . '' . esc_html( $explain['title'] ) . '' . esc_html( $row['message'] ) . '
'; } self::render_blind_spots(); } private static function render_blind_spots() { $edge = AgentStamp_Readiness::edge_from_headers( AgentStamp_Sensor::request()->headers ); if ( '' !== $edge ) { echo '

' . esc_html( sprintf( /* translators: 1: edge network, 2: where to look */ __( '%1$s is in front of this store. Agents it refuses never reach WordPress, so they are not counted here. In %1$s, %2$s', 'agent-stamp' ), $edge, AgentStamp_Readiness::edge_settings( $edge ) ) ) . '

'; } $cache = AgentStamp_Readiness::page_cache( defined( 'WP_CACHE' ) && WP_CACHE, file_exists( WP_CONTENT_DIR . '/advanced-cache.php' ), (array) get_option( 'active_plugins', array() ) ); if ( '' !== $cache ) { echo '

' . esc_html( sprintf( /* translators: %s: page cache plugin, or "A page cache" */ __( '%s is on. Pages it serves from cache never run WordPress, so agent visits to them are not counted here. Cart and checkout are normally not cached, so those steps still count.', 'agent-stamp' ), $cache ) ) . '

'; } echo '

' . esc_html__( 'What this cannot see: requests a firewall or CDN refused before WordPress ran (check your Cloudflare or host AI-bot setting), pages a full-page cache served, and agent browsers that look like an ordinary person until checkout. Cart value is the cart at the moment the store stopped the agent.', 'agent-stamp' ) . '

'; } public static function render_readiness() { $kept = min( 30, AgentStamp_Events::keep_days() ); $funnel = AgentStamp_Sensor::summary( $kept ); $readiness = AgentStamp_Readiness::check( AgentStamp_Readiness::facts( $funnel['blocked'], $kept ) ); echo '

' . esc_html( sprintf( /* translators: %d: score out of 100 */ __( 'Agent readiness: %d / 100', 'agent-stamp' ), $readiness['score'] ) ) . '

'; echo '

' . esc_html__( 'Can an AI agent find your products, read them, and check out? Each line says what to change.', 'agent-stamp' ) . '

'; $icons = array( 'pass' => '✔', 'warn' => '!', 'fail' => '✖', ); $colors = array( 'pass' => '#008a20', 'warn' => '#996800', 'fail' => '#d63638', ); echo ''; foreach ( $readiness['checks'] as $check ) { echo ''; echo ''; } echo '
' . esc_html( $icons[ $check['status'] ] ) . '' . esc_html( $check['title'] ) . '' . esc_html( $check['detail'] ); if ( '' !== $check['fix'] ) { echo '
' . esc_html( $check['fix'] ) . ''; } echo '
'; if ( '' !== $check['action'] ) { echo '
'; wp_nonce_field( 'agent_stamp_fix' ); echo ''; echo '
'; } echo '
'; echo '

' . esc_html__( 'Share the outside view of any store at getagentstamp.com/check.', 'agent-stamp' ) . '

'; } public static function apply_fix() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You cannot change this setting.', 'agent-stamp' ), 403 ); } check_admin_referer( 'agent_stamp_fix' ); $fix = isset( $_POST['fix'] ) ? sanitize_key( wp_unslash( $_POST['fix'] ) ) : ''; if ( 'enable_guest_checkout' === $fix ) { update_option( 'woocommerce_enable_guest_checkout', 'yes' ); } elseif ( 'enable_fast_lane' === $fix ) { $saved = get_option( AgentStamp_Policy::OPTION, array() ); $saved = is_array( $saved ) ? $saved : array(); $saved['fast_lane'] = true; update_option( AgentStamp_Policy::OPTION, AgentStamp_Policy::sanitize( $saved ) ); } wp_safe_redirect( self::url( 'readiness', array( 'agent_stamp_done' => 1 ) ) ); exit; } public static function render_rules() { $saved = get_option( AgentStamp_Policy::OPTION, array() ); $rules = AgentStamp_Policy::sanitize( is_array( $saved ) ? $saved : array() ); $hold = array( 'allow' => __( 'Allow', 'agent-stamp' ), 'hold' => __( 'Allow, then put the paid order on hold for review', 'agent-stamp' ), ); $refuse = $hold + array( 'block' => __( 'Refuse, and ask the agent to identify itself', 'agent-stamp' ), ); echo '

' . esc_html__( 'Each row is one kind of checkout. Nothing is blocked or held unless you choose it here. An agent with a verified signature is always accepted.', 'agent-stamp' ) . '

'; echo '
'; wp_nonce_field( 'agent_stamp_rules' ); echo ''; echo ''; echo ''; self::policy_row( __( 'Named by the payment', 'agent-stamp' ), __( 'No checkout rule. Stripe or PayPal named the agent on the payment.', 'agent-stamp' ) ); self::policy_row( __( 'Said its name, did not sign', 'agent-stamp' ), __( 'No checkout rule. The user-agent or the agent name field named a product, and the request was not signed. Anyone can send that name.', 'agent-stamp' ) ); self::policy_choice( __( 'Likely agent', 'agent-stamp' ), 'likely', $rules['likely'], $hold, __( 'A person may have placed it, so it is never refused.', 'agent-stamp' ) ); self::policy_choice( __( 'Unattended', 'agent-stamp' ), 'unattended', $rules['unattended'], $refuse, __( 'No person used the place-order control, and no agent named itself.', 'agent-stamp' ) ); self::policy_row( __( 'This store\'s REST API', 'agent-stamp' ), __( 'No checkout rule. Subscriptions, Zapier, and your own integrations create orders this way.', 'agent-stamp' ) ); self::policy_row( __( 'Agent protocol', 'agent-stamp' ), __( 'No checkout rule. The order came through UCP, ACP, MCP, or another agent protocol.', 'agent-stamp' ) ); self::policy_row( __( 'Unknown', 'agent-stamp' ), __( 'No checkout rule. A failed signature, a crawler, or a request this plugin will not name. It is not a fraud verdict.', 'agent-stamp' ) ); self::policy_row( __( 'A person', 'agent-stamp' ), __( 'No checkout rule. An ordinary browser, and a person used the place-order control.', 'agent-stamp' ) ); echo '
' . esc_html__( 'Who', 'agent-stamp' ) . '' . esc_html__( 'Checkout', 'agent-stamp' ) . '
' . esc_html__( 'Signed agent', 'agent-stamp' ) . ''; echo esc_html__( 'Always accepted.', 'agent-stamp' ) . '
'; echo ''; do_action( 'agent_stamp_rules_fields' ); echo ''; if ( ! defined( 'AGENT_STAMP_PRO_VERSION' ) ) { echo '

' . esc_html__( 'Agent Stamp Pro is a separate plugin. It adds a card-testing guard, an email asking the buyer to confirm an agent order, holding the order until they do, and dispute replies.', 'agent-stamp' ) . ' '; self::pro_link(); echo '

'; } $digest = 'no' !== get_option( 'agent_stamp_send_digest', 'yes' ); echo '

' . esc_html__( 'Email', 'agent-stamp' ) . '

'; echo '

'; echo '

'; } /** * A row with no control. The text is already translated. * * @param string $who * @param string $text */ private static function policy_row( $who, $text ) { echo '' . esc_html( $who ) . '' . esc_html( $text ) . ''; } /** * A row with an allow, hold, or refuse choice. Labels are already translated. * * @param string $who * @param string $key * @param string $current * @param array $options * @param string $help */ private static function policy_choice( $who, $key, $current, array $options, $help ) { echo '' . esc_html( $who ) . '

' . esc_html( $help ) . '

'; } public static function save_rules() { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'You cannot change these rules.', 'agent-stamp' ), 403 ); } check_admin_referer( 'agent_stamp_rules' ); $input = isset( $_POST['rules'] ) && is_array( $_POST['rules'] ) ? map_deep( wp_unslash( $_POST['rules'] ), 'sanitize_text_field' ) : array(); update_option( AgentStamp_Policy::OPTION, AgentStamp_Policy::sanitize( $input ) ); update_option( 'agent_stamp_send_digest', empty( $_POST['agent_stamp_send_digest'] ) ? 'no' : 'yes' ); do_action( 'agent_stamp_rules_saved' ); wp_safe_redirect( self::url( 'rules', array( 'agent_stamp_done' => 1 ) ) ); exit; } }