container = $container;
}
/**
* Schedule the weekly digest cron event.
*
* @return void
*/
public function schedule(): void {
if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
wp_schedule_event( time(), 'weekly', self::CRON_HOOK );
}
}
/**
* Unschedule the weekly digest.
*
* @return void
*/
public function unschedule(): void {
$timestamp = wp_next_scheduled( self::CRON_HOOK );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, self::CRON_HOOK );
}
}
/**
* Send the weekly digest email.
*
* @return void
*/
public function send(): void {
if ( ! aicommerce_is_pro() ) {
return;
}
$recipients = array( get_option( 'admin_email' ) );
$recipients = array_filter( array_unique( $recipients ) );
if ( empty( $recipients ) ) {
return;
}
$plugin_name = WhiteLabel::get_plugin_name();
$site_name = get_bloginfo( 'name' );
$subject = sprintf(
/* translators: 1: Plugin name 2: Site name */
__( '%1$s Weekly AI Visibility Report — %2$s', 'ai-discoverability-kit' ),
$plugin_name,
$site_name
);
$body = $this->build_email_body( $plugin_name, $site_name );
// Set HTML content type.
add_filter( 'wp_mail_content_type', array( $this, 'set_html_content_type' ) );
wp_mail( $recipients, $subject, $body );
// Remove HTML content type filter.
remove_filter( 'wp_mail_content_type', array( $this, 'set_html_content_type' ) );
}
/**
* Set HTML content type for wp_mail.
*
* @return string
*/
public function set_html_content_type(): string {
return 'text/html';
}
/**
* Build the HTML email body.
*
* @param string $plugin_name The plugin display name.
* @param string $site_name The site name.
*
* @return string HTML email content.
*/
private function build_email_body( string $plugin_name, string $site_name ): string {
$score_data = $this->get_score_data();
$issues = $this->get_top_issues( 3 );
$feeds = $this->get_feed_status();
$bot_visits = $this->get_bot_visits_7d();
$revenue = $this->get_revenue_7d();
$soa_data = $this->get_soa_data();
$agent_count = $this->get_agent_requests_7d();
$logo_url = WhiteLabel::get_logo_url();
$primary = get_option( 'aicommerce_wl_primary_color', '#00e58a' );
$dashboard_url = admin_url( 'admin.php?page=tyralix-aicommerce' );
$overall_score = $score_data['overall'] ?? 0;
$trend = $score_data['trend'] ?? null;
$trend_text = '';
if ( $trend && isset( $trend['delta'] ) ) {
$delta = (int) $trend['delta'];
$direction = $delta >= 0 ? '+' : '';
$trend_text = " ({$direction}{$delta} vs last week)";
}
$html = '
';
// Header.
if ( $logo_url ) {
$html .= ' . ')
';
}
$html .= '' . esc_html( $plugin_name ) . '
';
$html .= 'Weekly AI Visibility Report for ' . esc_html( $site_name ) . '
';
$html .= '
';
// Overall score.
$html .= 'Overall AI Readiness Score
';
$html .= '' . (int) $overall_score . '/100' . esc_html( $trend_text ) . '
';
// Top issues.
if ( ! empty( $issues ) ) {
$html .= 'Top Issues to Fix
';
$html .= '';
foreach ( $issues as $issue ) {
$html .= '- ' . esc_html( $issue['title'] ?? '' );
if ( ! empty( $issue['estimated_score_gain'] ) ) {
$html .= ' (+' . (int) $issue['estimated_score_gain'] . ' pts)';
}
$html .= '
';
}
$html .= '
';
}
// Revenue (Business only).
if ( null !== $revenue ) {
$html .= 'AI-Attributed Revenue (7 days)
';
$html .= '$' . number_format( $revenue['total_revenue'] ?? 0, 2 ) . '
';
$html .= '' . (int) ( $revenue['order_count'] ?? 0 ) . ' orders
';
}
// Share of Answer.
if ( ! empty( $soa_data ) && ! empty( $soa_data['by_platform'] ) ) {
$html .= 'Share of Answer This Week
';
$html .= '';
$html .= '| Platform | SOA% | Change |
';
foreach ( $soa_data['by_platform'] as $platform => $data ) {
$change_color = ( $data['change'] ?? 0 ) >= 0 ? '#00a32a' : '#d63638';
$change_text = ( $data['change'] ?? 0 ) >= 0 ? '+' . $data['change'] : (string) $data['change'];
$html .= '';
$html .= '| ' . esc_html( ucfirst( $platform ) ) . ' | ';
$html .= '' . esc_html( $data['current'] . '%' ) . ' | ';
$html .= '' . esc_html( $change_text . '%' ) . ' | ';
$html .= '
';
}
$html .= '
';
}
// MCP Agent activity (Business only).
if ( $agent_count > 0 ) {
$html .= 'MCP Agent Activity (7 days)
';
$html .= '' . number_format( $agent_count ) . ' requests
';
}
// Feed status.
if ( ! empty( $feeds ) ) {
$html .= 'Feed Status
';
$html .= '';
foreach ( $feeds as $name => $status ) {
$html .= '';
$html .= '| ' . esc_html( ucfirst( $name ) ) . ' | ';
$html .= '' . esc_html( $status ) . ' | ';
$html .= '
';
}
$html .= '
';
}
// Bot visits.
if ( $bot_visits > 0 ) {
$html .= 'AI Bot Visits (7 days)
';
$html .= '' . number_format( $bot_visits ) . '
';
}
// CTA.
$html .= '';
// Footer.
$html .= '
';
$html .= '';
return $html;
}
/**
* Get current score data.
*
* @return array Score data array.
*/
private function get_score_data(): array {
if ( ! $this->container->has( ReadinessScore::class ) ) {
return array( 'overall' => 0 );
}
/** @var ReadinessScore $score */
$score = $this->container->get( ReadinessScore::class );
return $score->calculate_store_score();
}
/**
* Get top N issues.
*
* @param int $count Number of issues to return.
*
* @return array Top issues.
*/
private function get_top_issues( int $count ): array {
if ( ! $this->container->has( ReadinessScore::class ) ) {
return array();
}
/** @var ReadinessScore $score */
$score = $this->container->get( ReadinessScore::class );
$issues = $score->get_issues();
return array_slice( $issues, 0, $count );
}
/**
* Get feed generation status.
*
* @return array Feed name => last generated timestamp or 'Never'.
*/
private function get_feed_status(): array {
$feeds = get_option( 'aicommerce_feed_types', array() );
$status = array();
if ( is_array( $feeds ) ) {
foreach ( $feeds as $feed ) {
$name = $feed['type'] ?? $feed;
$last = get_option( "aicommerce_feed_{$name}_last_generated", '' );
$status[ $name ] = $last ?: __( 'Never', 'ai-discoverability-kit' );
}
}
return $status;
}
/**
* Get bot visit count for the last 7 days.
*
* @return int Total visits.
*/
private function get_bot_visits_7d(): int {
global $wpdb;
$table = "{$wpdb->prefix}aicommerce_crawler_log";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$table} WHERE request_time >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
gmdate( 'Y-m-d H:i:s', strtotime( '-7 days' ) )
)
);
return (int) $count;
}
/**
* Get AI-attributed revenue for the last 7 days (Business tier only).
*
* @return array|null Revenue data or null if not Business tier.
*/
private function get_revenue_7d(): ?array {
if ( ! function_exists( 'aicommerce_is_business' ) || ! aicommerce_is_business() ) {
return null;
}
if ( ! $this->container->has( AttributionTracker::class ) ) {
return null;
}
/** @var AttributionTracker $tracker */
$tracker = $this->container->get( AttributionTracker::class );
return $tracker->get_summary( 7 );
}
/**
* Get SOA data for the last 7 days.
*
* @return array SOA summary or empty array.
*/
private function get_soa_data(): array {
if ( ! $this->container->has( SOATracker::class ) ) {
return array();
}
/** @var SOATracker $soa */
$soa = $this->container->get( SOATracker::class );
return $soa->get_soa_summary( 7 );
}
/**
* Get MCP agent request count for the last 7 days.
*
* @return int Request count.
*/
private function get_agent_requests_7d(): int {
global $wpdb;
$table = "{$wpdb->prefix}aicommerce_agent_log";
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$table} WHERE agent_platform = %s AND created_at >= %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'mcp',
gmdate( 'Y-m-d H:i:s', strtotime( '-7 days' ) )
)
);
return (int) $count;
}
}