200, 'return' => 'ids', 'date_created' => '>=' . $since, 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery array( 'key' => '_vsai_fraud_score', 'value' => 30, 'compare' => '>=', 'type' => 'NUMERIC', ), ), ) ); $failed = wc_get_orders( array( 'limit' => 200, 'return' => 'ids', 'status' => 'failed', 'date_created' => '>=' . $since, ) ); $stuck = 0; $pend = wc_get_orders( array( 'limit' => 50, 'status' => 'pending', 'date_created' => '<' . ( time() - 2 * HOUR_IN_SECONDS ), ) ); foreach ( (array) $pend as $o ) { $pm = method_exists( $o, 'get_payment_method' ) ? $o->get_payment_method() : ''; if ( $pm && ! in_array( $pm, array( 'cod', 'bacs', 'cheque' ), true ) && $o->get_transaction_id() ) { $stuck++; } } return array( 'fraud' => is_array( $fraud ) ? count( $fraud ) : 0, 'failed' => is_array( $failed ) ? count( $failed ) : 0, 'stuck' => $stuck, ); } /** Actualizaciones pendientes (mismas fuentes que el chequeo 14). */ function vsai_report_pending_updates() { $n = 0; $up = get_site_transient( 'update_plugins' ); if ( $up && ! empty( $up->response ) ) { $n += count( $up->response ); } $ut = get_site_transient( 'update_themes' ); if ( $ut && ! empty( $ut->response ) ) { $n += count( $ut->response ); } return $n; } /** * Construye el reporte: devuelve [ 'score' => int|null, 'trend' => string, * 'sections' => [ [titulo, [lineas...] ], ... ] ]. */ function vsai_report_build() { $scan = (array) get_option( 'vsai_scan', array() ); $score = function_exists( 'vsai_scanner_score' ) ? vsai_scanner_score( $scan ) : null; $hist = (array) get_option( 'vsai_report_hist', array() ); $trend = ''; $prev = null; foreach ( array_reverse( $hist ) as $h ) { if ( isset( $h['score'] ) && null !== $h['score'] && (int) $h['score'] > 0 ) { $prev = (int) $h['score']; break; } } if ( null !== $score && null !== $prev ) { $diff = $score - $prev; if ( $diff > 0 ) { $trend = sprintf( /* translators: %d: points the security score changed since the last report. */ __( '▲ %d points since the last report', 'vidal-security-ai' ), $diff ); } elseif ( $diff < 0 ) { $trend = sprintf( /* translators: %d: points the security score changed since the last report. */ __( '▼ %d points since the last report', 'vidal-security-ai' ), abs( $diff ) ); } else { $trend = __( 'Same as the last report', 'vidal-security-ai' ); } } $sections = array(); // Escaneo: riesgos (fail) y atenciones (warn) vivos, con su explicación. if ( ! empty( $scan['checks'] ) ) { $risk = array(); $warn = array(); foreach ( (array) $scan['checks'] as $c ) { $line = '• ' . ( $c['title'] ?? '' ); if ( ! empty( $c['detail'] ) ) { $d = trim( wp_strip_all_tags( (string) $c['detail'] ) ); if ( strlen( $d ) > 150 ) { $d = substr( $d, 0, 150 ) . '…'; } $line .= ' — ' . $d; } if ( 'fail' === ( $c['status'] ?? '' ) ) { $risk[] = $line; } elseif ( 'warn' === ( $c['status'] ?? '' ) ) { $warn[] = $line; } } $lines = array(); if ( $risk ) { $lines[] = __( 'Risks to fix:', 'vidal-security-ai' ); $lines = array_merge( $lines, $risk ); } if ( $warn ) { $lines[] = __( 'Worth a look:', 'vidal-security-ai' ); $lines = array_merge( $lines, $warn ); } if ( ! $lines ) { $lines[] = __( 'All 15 checks are green. Nothing to fix.', 'vidal-security-ai' ); } $sections[] = array( '🔍 ' . __( 'Security diagnosis', 'vidal-security-ai' ), $lines ); } // Escudo de bots. $bs = (array) get_option( 'vsai_bot_stats', array() ); $ledger = (array) get_option( 'vsai_bot_ledger', array() ); $nblocks = isset( $bs['blocks'] ) && is_array( $bs['blocks'] ) ? count( $bs['blocks'] ) : (int) ( $bs['blocks'] ?? 0 ); $ledger_blocks = 0; foreach ( $ledger as $e ) { $ledger_blocks += (int) ( $e['blocks'] ?? 0 ); } $nblocks = max( $nblocks, $ledger_blocks ); $lines = array( sprintf( /* translators: 1: blocks applied by the bot shield so far. 2: IP addresses being watched. */ __( '%1$d blocks applied so far · %2$d IPs being watched.', 'vidal-security-ai' ), $nblocks, count( $ledger ) ), ); $now = time(); $blocked = array_filter( $ledger, function ( $e ) use ( $now ) { return ! empty( $e['blocked_until'] ) && (int) $e['blocked_until'] > $now; } ); if ( $blocked ) { $lines[] = __( 'Blocked right now:', 'vidal-security-ai' ); foreach ( array_slice( $blocked, 0, 5, true ) as $ip => $e ) { $lines[] = '• ' . $ip . ' (' . (int) ( $e['points'] ?? 0 ) . ' pts)'; } } $good_bots = function_exists( 'vsai_bot_good_list' ) ? vsai_bot_good_list() : array(); if ( $good_bots ) { $gnames = array(); foreach ( $good_bots as $g ) { if ( ! empty( $g['name'] ) ) { $gnames[ $g['name'] ] = true; } } $lines[] = sprintf( /* translators: 1: number of verified good bots. 2: their names, separated by commas. */ __( '✅ Good bots verified (never blocked): %1$d — %2$s.', 'vidal-security-ai' ), count( $good_bots ), implode( ', ', array_keys( $gnames ) ) ); } $sections[] = array( '🤖 ' . __( 'Bot shield', 'vidal-security-ai' ), $lines ); // Guardián IA (el parte del cerebro, como en el panel). if ( function_exists( 'vsai_premium_fetch_report' ) ) { $data = vsai_premium_fetch_report(); $parte = ''; if ( is_array( $data ) ) { foreach ( array( 'parte', 'text', 'summary', 'report' ) as $k ) { if ( ! empty( $data[ $k ] ) && is_string( $data[ $k ] ) ) { $parte = $data[ $k ]; break; } } } if ( '' !== $parte ) { $glines = array_values( array_filter( array_map( 'trim', explode( "\n", $parte ) ) ) ); if ( $glines && ( false !== stripos( $glines[0], 'Parte del guardia' ) || false !== stripos( $glines[0], 'Guardian report' ) ) ) { array_shift( $glines ); } if ( $glines ) { $glines = array_map( function ( $l ) { return '• ' . ltrim( $l, '-–— ' ); }, $glines ); $sections[] = array( '🧠 ' . __( 'AI Guardian', 'vidal-security-ai' ), $glines ); } } } // Tienda. $woo = vsai_report_woo_counts(); if ( null !== $woo ) { $sections[] = array( '🛒 ' . __( 'Your store this week', 'vidal-security-ai' ), array( sprintf( /* translators: %d: number of orders flagged by the anti-fraud score. */ __( 'Orders flagged by the anti-fraud score: %d', 'vidal-security-ai' ), $woo['fraud'] ), sprintf( /* translators: %d: number of failed payments. */ __( 'Failed payments: %d', 'vidal-security-ai' ), $woo['failed'] ), sprintf( /* translators: %d: number of paid orders that are stuck. */ __( 'Paid orders stuck: %d', 'vidal-security-ai' ), $woo['stuck'] ), ), ); } // Archivos y actualizaciones. $last = (array) get_option( 'vsai_file_last', array() ); $flines = array(); $flines[] = ! empty( $last['issues'] ) ? sprintf( /* translators: %d: number of unexplained file changes. */ __( '⚠️ %d unexplained file change(s) — check the panel.', 'vidal-security-ai' ), count( $last['issues'] ) ) : __( 'Files in place; only update-related changes.', 'vidal-security-ai' ); if ( ! empty( $last['partial'] ) ) { $flines = array( __( 'The daily snapshot was partial. The previous baseline was retained; review the deep file scan.', 'vidal-security-ai' ) ); } $pend = vsai_report_pending_updates(); $flines[] = $pend > 0 ? sprintf( /* translators: %d: number of pending updates. */ __( '%d update(s) pending — staying current is your first shield.', 'vidal-security-ai' ), $pend ) : __( 'Everything is up to date.', 'vidal-security-ai' ); $sections[] = array( '🗂️ ' . __( 'Files & updates', 'vidal-security-ai' ), $flines ); return array( 'score' => $score, 'trend' => $trend, 'sections' => $sections, ); } /** Envía el reporte ahora. Devuelve true si wp_mail aceptó. */ function vsai_report_send() { $to = get_option( 'admin_email' ); if ( ! $to ) { return false; } $r = vsai_report_build(); $site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ); $panel = admin_url( 'admin.php?page=vidal-security-ai' ); $score_txt = null !== $r['score'] ? $r['score'] . '/100' : '—'; // HTML (el cuerpo lleva el texto visible completo de cada sección). $body = ''; foreach ( $r['sections'] as $sec ) { $body .= '
| '
. ''
. ' ' . esc_html__( 'You receive this report because Vidal Security AI protects this site. Change the frequency or turn it off in the panel.', 'vidal-security-ai' ) . ' '
. ' |