' ), 65 ); } add_action( 'admin_menu', 'vsai_admin_menu' ); /** * Carga la hoja de estilos propia ÚNICAMENTE en la página del plugin. * * @param string $hook Hook suffix de la pantalla actual. */ function vsai_admin_assets( $hook ) { if ( 'toplevel_page_vidal-security-ai' !== $hook ) { return; } wp_enqueue_style( 'vsai-admin', VSAI_URL . 'assets/css/admin.css', array(), VSAI_VERSION ); wp_enqueue_script( 'vsai-admin', VSAI_URL . 'assets/js/vsai-admin.js', array(), VSAI_VERSION, true ); } add_action( 'admin_enqueue_scripts', 'vsai_admin_assets' ); /** * HTML permitido al imprimir las tarjetas de los módulos en el panel: * el conjunto «post» de WordPress más los controles de formulario y el * SVG animado de Bruno que las tarjetas usan de verdad. * * @return array Lista para wp_kses(). */ function vsai_allowed_panel_html() { $box = array( 'class' => true, 'id' => true, 'style' => true, 'title' => true, 'aria-label' => true ); $allowed = wp_kses_allowed_html( 'post' ); $allowed['section'] = $box; $allowed['form'] = $box + array( 'method' => true, 'action' => true ); $allowed['input'] = $box + array( 'type' => true, 'name' => true, 'value' => true, 'checked' => true, 'disabled' => true, 'readonly' => true, 'placeholder' => true, 'min' => true, 'max' => true, 'step' => true, 'size' => true, 'maxlength' => true ); $allowed['button'] = $box + array( 'type' => true, 'name' => true, 'value' => true, 'disabled' => true ); $allowed['select'] = $box + array( 'name' => true, 'disabled' => true ); $allowed['option'] = array( 'value' => true, 'selected' => true ); $allowed['textarea'] = $box + array( 'name' => true, 'rows' => true, 'cols' => true, 'placeholder' => true, 'readonly' => true, 'disabled' => true ); $allowed['label'] = $box + array( 'for' => true ); $allowed['div'] = ( isset( $allowed['div'] ) ? $allowed['div'] : array() ) + array( 'data-view' => true, 'data-otpauth' => true ); $allowed['a'] = ( isset( $allowed['a'] ) ? $allowed['a'] : array() ) + array( 'target' => true, 'data-view' => true ); $paint = $box + array( 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-dasharray' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'opacity' => true, 'transform' => true ); $allowed['svg'] = $paint + array( 'width' => true, 'height' => true, 'viewbox' => true, 'role' => true, 'xmlns' => true ); foreach ( array( 'circle', 'ellipse', 'rect', 'path', 'g', 'defs' ) as $tag ) { $allowed[ $tag ] = $paint + array( 'cx' => true, 'cy' => true, 'r' => true, 'rx' => true, 'ry' => true, 'x' => true, 'y' => true, 'width' => true, 'height' => true, 'd' => true ); } $allowed['animate'] = array( 'attributename' => true, 'values' => true, 'keytimes' => true, 'dur' => true, 'repeatcount' => true, 'fill' => true ); $allowed['animatetransform'] = $allowed['animate'] + array( 'type' => true, 'from' => true, 'to' => true ); return $allowed; } /** * Imprime el HTML de una tarjeta escapándolo con wp_kses() en el momento * de la salida. Los módulos ya escapan cada valor al construir su HTML; * esto es la red final: fuera scripts, manejadores inline y cualquier * etiqueta o atributo que el panel no usa. * * @param string $html HTML construido por un módulo del panel. */ function vsai_print_panel_html( $html ) { echo wp_kses( (string) $html, vsai_allowed_panel_html() ); } /** * Textos de los avisos tras guardar/escanear (clave => [tipo, texto]). * * @return array */ function vsai_admin_notices_map() { return array( 'scan_done' => array( 'ok', __( 'Scan complete: this is the current picture of your site.', 'vidal-security-ai' ) ), 'settings_saved' => array( 'ok', __( 'Hardening saved and applied.', 'vidal-security-ai' ) ), 'prem_saved' => array( 'ok', __( 'AI Guardian settings saved.', 'vidal-security-ai' ) ), 'bot_updated' => array( 'ok', __( 'Bot shield list updated.', 'vidal-security-ai' ) ), 'deep_done' => array( 'ok', __( 'Deep file scan complete: the results are in the Deep file scan card.', 'vidal-security-ai' ) ), 'deep_known' => array( 'ok', __( 'Marked as known: that exact file will not be flagged again unless it changes.', 'vidal-security-ai' ) ), 'deep_consent' => array( 'ok', __( 'Core verification preference saved.', 'vidal-security-ai' ) ), 'prem_test_ok' => array( 'ok', __( 'Connection OK: your Guardian answered from the cloud. All set.', 'vidal-security-ai' ) ), 'prem_test_fail' => array( 'fail', __( 'The connection test failed. Check the key and the server URL and try again.', 'vidal-security-ai' ) ), 'htaccess_warn' => array( 'warn', __( 'Note: the readme.html block could not be written to .htaccess (not writable, or your server is not Apache). The rest of the settings were applied.', 'vidal-security-ai' ) ), ); } /** * Nivel visual de una puntuación. * * @param int|null $score Puntuación 0–100 o null. * @return string 'none' | 'low' | 'mid' | 'high' */ function vsai_score_level( $score ) { if ( null === $score ) { return 'none'; } if ( $score < 50 ) { return 'low'; } if ( $score < 80 ) { return 'mid'; } return 'high'; } /** * Pinta la página completa del plugin. */ function vsai_render_admin_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to view this page.', 'vidal-security-ai' ) ); } $scan = get_option( 'vsai_scan', array() ); $score = function_exists( 'vsai_scanner_score' ) ? vsai_scanner_score( $scan ) : null; $level = vsai_score_level( $score ); $settings = vsai_get_settings(); $defs = vsai_hardening_definitions(); $level_words = array( 'none' => __( 'Not scanned yet', 'vidal-security-ai' ), 'low' => __( 'At risk', 'vidal-security-ai' ), 'mid' => __( 'Could be better', 'vidal-security-ai' ), 'high' => __( 'Well protected', 'vidal-security-ai' ), ); $badge_words = array( 'ok' => __( 'OK', 'vidal-security-ai' ), 'warn' => __( 'Attention', 'vidal-security-ai' ), 'fail' => __( 'Risk', 'vidal-security-ai' ), ); // Medidor circular SVG: r=58, circunferencia ≈ 364.42. $circ = 364.42; $offset = ( null === $score ) ? $circ : $circ * ( 1 - $score / 100 ); $notices = vsai_admin_notices_map(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Solo lee qué aviso mostrar; las acciones se validan con nonce en sus handlers. $notice_key = isset( $_GET['vsai_notice'] ) ? sanitize_key( wp_unslash( $_GET['vsai_notice'] ) ) : ''; ?>

Vidal Security AI

/ 100

'🏠 ' . __( 'Home', 'vidal-security-ai' ), 'centinela' => '👁️ ' . __( 'Sentinel', 'vidal-security-ai' ), 'protecciones' => '🔒 ' . __( 'Protections', 'vidal-security-ai' ), 'rondas' => '📋 ' . __( 'Rounds', 'vidal-security-ai' ), 'ajustes' => '⚙️ ' . __( 'Settings', 'vidal-security-ai' ), ); // Datos de las baldosas (solo opciones guardadas: sin llamadas externas). $guardian_on = function_exists( 'vsai_premium_is_active' ) && vsai_premium_is_active(); $rep_opt = get_option( 'vsai_last_report', array() ); $ev24 = ( is_array( $rep_opt ) && isset( $rep_opt['data']['totals']['events'] ) ) ? (int) $rep_opt['data']['totals']['events'] : null; $rounds_t = function_exists( 'vsai_sentinel_get_rounds' ) ? vsai_sentinel_get_rounds() : array(); $hard_on_n = 0; foreach ( array_keys( $defs ) as $hk ) { if ( ! empty( $settings[ $hk ] ) ) { $hard_on_n++; } } $hard_total = count( $defs ); $brain_lbl = ( function_exists( 'vsai_premium_brain' ) && 'flow' === vsai_premium_brain() ) ? 'Vidal Flow' : 'n8n'; $woo_n = null; if ( class_exists( 'WooCommerce' ) ) { $woo_n = 0; foreach ( array( 'vsai_antifraud_active', 'vsai_cg_active', 'vsai_pay_active' ) as $wf ) { if ( function_exists( $wf ) && $wf() ) { $woo_n++; } } } ?>
👁️

→
🔒


→
📋

→
⚙️

→
🛒

→

🛡️

🛒

  • ⚠️

🛒

🔍

🛰️

🧰

$def ) : ?>

⚠️

🎫

home_url(), 'v' => VSAI_VERSION, 'lang' => $vsai_lang, ), 'es' === $vsai_lang ? 'https://vidaldigitalmiami.com/vidal-security-ai-soporte.html' : 'https://vidaldigitalmiami.com/vidal-security-ai-support.html' ); ?>