'customer.name', __( 'Orders', 'statbird' ) => 'customer.orders', __( 'Spent', 'statbird' ) => 'customer.spent', __( 'Churn risk', 'statbird' ) => 'customer.churn_risk', ); $events['statbird.segment_changed'] = array( 'label' => __( 'Statbird: customer changed group', 'statbird' ), 'group' => 'woocommerce', 'title' => __( '{customer.name} moved from {from_label} to {to_label}', 'statbird' ), 'fields' => $fields, 'link' => 'customer.admin_url', 'color' => '#0f6e66', ); $events['statbird.at_risk'] = array( 'label' => __( 'Statbird: valuable customer at risk', 'statbird' ), 'group' => 'woocommerce', 'title' => __( 'At risk: {customer.name} ({to_label})', 'statbird' ), 'fields' => $fields, 'link' => 'customer.admin_url', 'color' => '#b86e00', ); $events['statbird.champion'] = array( 'label' => __( 'Statbird: new Champion', 'statbird' ), 'group' => 'woocommerce', 'title' => __( 'New Champion: {customer.name}', 'statbird' ), 'fields' => $fields, 'link' => 'customer.admin_url', 'color' => '#0f6e66', ); $events['statbird.weekly_summary'] = array( 'label' => __( 'Statbird: weekly customer briefing', 'statbird' ), 'group' => 'woocommerce', 'title' => __( 'Customer briefing for the week of {week}', 'statbird' ), 'fields' => array( __( 'Summary', 'statbird' ) => 'summary', __( 'Repeat rate', 'statbird' ) => 'repeat_rate', __( 'New customers', 'statbird' ) => 'new_customers', __( 'Revenue at risk', 'statbird' ) => 'revenue_at_risk', ), 'link' => 'dashboard_url', 'color' => '#0f6e66', ); return $events; } /** * Sends group-change events. * * @param string $customer_key MD5 key. * @param string $from Previous group. * @param string $to New group. */ public static function segment_changed( $customer_key, $from, $to ) { if ( ! self::active() || self::$sent >= self::PER_RUN ) { return; } global $wpdb; $t = Store::table(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name only. $c = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $t WHERE customer_key = %s", $customer_key ), ARRAY_A ); if ( ! $c ) { return; } $groups = Scoring::groups(); $data = array( 'customer' => array( 'name' => '' !== $c['name'] ? $c['name'] : $c['email'], 'email' => $c['email'], 'orders' => (int) $c['orders'], 'spent' => wc_format_decimal( $c['spent'], 2 ), 'churn_risk' => (int) $c['churn_risk'] . '%', 'admin_url' => Admin::customer_url( $c ), ), 'from' => $from, 'to' => $to, 'from_label' => $groups[ $from ]['label'] ?? $from, 'to_label' => $groups[ $to ]['label'] ?? $to, ); hookbird_dispatch( 'statbird.segment_changed', $data ); if ( in_array( $to, array( 'at_risk', 'cant_lose' ), true ) && (int) $c['orders'] > 1 ) { hookbird_dispatch( 'statbird.at_risk', $data ); } if ( 'champions' === $to ) { hookbird_dispatch( 'statbird.champion', $data ); } ++self::$sent; } /** * Schedules the weekly briefing for Monday 09:00 site time. */ public static function schedule() { if ( ! function_exists( 'as_has_scheduled_action' ) || as_has_scheduled_action( self::WEEKLY, array(), Rebuild::GROUP ) ) { return; } $next = strtotime( 'next monday 09:00', current_time( 'timestamp' ) ) - (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested -- converting a local wall-clock time to UTC. as_schedule_recurring_action( $next, WEEK_IN_SECONDS, self::WEEKLY, array(), Rebuild::GROUP ); } /** * Builds and sends the weekly briefing. * * @return array|null Payload, or null when not sent. */ public static function weekly() { if ( ! self::active() || ! Settings::get( 'weekly_briefing' ) ) { return null; } $snap = Insights::get(); $k = $snap['kpis'] ?? array(); $summary = ''; if ( AI::available() ) { $ai = AI::summary( true ); if ( ! is_wp_error( $ai ) ) { $summary = $ai; } } if ( '' === $summary ) { /* translators: 1: active customers, 2: repeat customers at risk. */ $summary = sprintf( __( '%1$d active customers. %2$d repeat customers are likely to stop buying.', 'statbird' ), (int) ( $k['active'] ?? 0 ), (int) ( $k['at_risk_customers'] ?? 0 ) ); } $data = array( 'week' => wp_date( get_option( 'date_format' ), time() - WEEK_IN_SECONDS ), 'summary' => $summary, 'repeat_rate' => ( $k['repeat_rate'] ?? 0 ) . '%', 'new_customers' => (int) ( $k['new_30d'] ?? 0 ), 'revenue_at_risk' => wc_format_decimal( $k['revenue_at_risk'] ?? 0, 2 ), 'anomalies' => $snap['anomalies'] ?? array(), 'dashboard_url' => admin_url( 'admin.php?page=statbird' ), ); hookbird_dispatch( 'statbird.weekly_summary', $data ); return $data; } }