wp_generate_uuid4(), 'secret' => wp_generate_password( 64, false, false ), 'email' => $email, 'site_url' => $site_url, 'status' => 'pending', 'requested_at' => 0, 'verification_requested_at' => 0, 'checked_at' => 0, 'last_error' => '', ); update_option( afroiabo_installation_option_key(), $credentials, false ); } return $credentials; } function afroiabo_save_installation_credentials( $credentials ) { update_option( afroiabo_installation_option_key(), $credentials, false ); } function afroiabo_public_user_reference() { if ( afroiabo_is_local_preview() ) { return 'afroditabot-local-preview'; } $credentials = afroiabo_get_installation_credentials(); return sanitize_text_field( $credentials['installation_id'] ?? '' ); } function afroiabo_backend_post( $path, $payload ) { $response = wp_remote_post( afroiabo_backend_url() . $path, array( 'timeout' => 12, 'sslverify' => true, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $payload ), ) ); if ( is_wp_error( $response ) ) { return $response; } $status = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $status < 200 || $status >= 300 || ! is_array( $body ) ) { $message = is_array( $body ) && ! empty( $body['error'] ) ? sanitize_text_field( $body['error'] ) : 'El servicio de AfroditaBot no ha podido completar la solicitud.'; return new WP_Error( 'afroiabo_backend_request', $message, array( 'status' => $status ) ); } return $body; } function afroiabo_request_installation_verification( $force = false ) { if ( afroiabo_is_local_preview() ) { return array( 'status' => 'verified' ); } $credentials = afroiabo_get_installation_credentials(); if ( 'verified' === ( $credentials['status'] ?? '' ) ) { return array( 'status' => 'verified' ); } $last_request = absint( $credentials['requested_at'] ?? 0 ); if ( ! $force && $last_request > time() - 15 * MINUTE_IN_SECONDS ) { return array( 'status' => $credentials['status'] ?? 'pending' ); } $result = afroiabo_backend_post( '/api/wp-installations/start', array( 'installationId' => $credentials['installation_id'], 'secret' => $credentials['secret'], 'email' => $credentials['email'], 'siteUrl' => $credentials['site_url'], ) ); $credentials['requested_at'] = time(); if ( is_wp_error( $result ) ) { $credentials['last_error'] = $result->get_error_message(); afroiabo_save_installation_credentials( $credentials ); return $result; } $credentials['status'] = sanitize_key( $result['status'] ?? 'pending' ); $credentials['verification_requested_at'] = time(); $credentials['last_error'] = ''; afroiabo_save_installation_credentials( $credentials ); return $result; } function afroiabo_refresh_installation_status() { if ( afroiabo_is_local_preview() ) { return array( 'status' => 'verified' ); } $credentials = afroiabo_get_installation_credentials(); $result = afroiabo_backend_post( '/api/wp-installations/status', array( 'installationId' => $credentials['installation_id'], 'secret' => $credentials['secret'], ) ); $credentials['checked_at'] = time(); if ( is_wp_error( $result ) ) { $credentials['last_error'] = $result->get_error_message(); afroiabo_save_installation_credentials( $credentials ); return $result; } $credentials['status'] = sanitize_key( $result['status'] ?? 'pending' ); $credentials['last_error'] = ''; afroiabo_save_installation_credentials( $credentials ); return $result; } function afroiabo_get_backend_token( $scope = 'widget' ) { $scope = ( 'admin' === $scope ) ? 'admin' : 'widget'; if ( afroiabo_is_local_preview() ) { return 'afroditabot-local-preview'; } $credentials = afroiabo_get_installation_credentials(); if ( 'verified' !== ( $credentials['status'] ?? '' ) ) { return new WP_Error( 'afroiabo_installation_pending', 'La instalación está pendiente de verificar por correo.' ); } $cache_key = 'afroiabo_token_' . md5( $scope . '|' . $credentials['installation_id'] ); $cached = get_transient( $cache_key ); if ( is_array( $cached ) && ! empty( $cached['token'] ) ) { return $cached['token']; } $result = afroiabo_backend_post( '/api/wp-token', array( 'installationId' => $credentials['installation_id'], 'secret' => $credentials['secret'], 'scope' => $scope, ) ); if ( is_wp_error( $result ) || empty( $result['token'] ) ) { return is_wp_error( $result ) ? $result : new WP_Error( 'afroiabo_backend_auth', 'No se pudo crear la sesión segura con AfroditaBot.' ); } $expires_in = isset( $result['expires_in'] ) ? absint( $result['expires_in'] ) : 900; $cache_ttl = max( 60, min( max( 120, $expires_in - 60 ), ( 'admin' === $scope ) ? 1800 : 600 ) ); set_transient( $cache_key, array( 'token' => $result['token'] ), $cache_ttl ); return $result['token']; } function afroiabo_preview_defaults( $admin_email = '' ) { $preview_reference = 'afroditabot-local-preview'; return array( 'Ssaldo' => '24.80', 'SMManual' => '¡Hola! Soy tu asistente. ¿En qué puedo ayudarte?', 'systemMessage2' => 'Responde de forma clara, útil y cercana usando la información del sitio.', 'Pregunta1' => '¿En qué puedo ayudarte hoy?', 'eventosk' => '', 'Model' => '12', 'MoP' => '1', 'token' => 'afroditabot-local-preview', 'userId' => $preview_reference, 'adminEmail2' => $preview_reference, ); } function afroiabo_runtime_config() { $admin_email = sanitize_email( get_option( 'admin_email' ) ); return array( 'apiBaseUrl' => afroiabo_backend_url(), 'localPreview' => afroiabo_is_local_preview(), 'adminEmail' => $admin_email, 'previewDefaults'=> afroiabo_preview_defaults( $admin_email ), ); } function afroiabo_add_admin_menu() { add_menu_page( 'Herramientas Afrodita', 'AfroditaBot', 'manage_options', 'afroiabo_kam_assistant_config', 'afroiabo_config_page', 'dashicons-format-chat', 66 ); add_submenu_page( 'afroiabo_kam_assistant_config', 'Leads de AfroditaBot', 'Leads', 'manage_options', 'afroiabo_leads', 'afroiabo_leads_page' ); } add_action( 'admin_menu', 'afroiabo_add_admin_menu' ); function afroiabo_register_lead_post_type() { register_post_type( 'afroiabo_lead', array( 'labels' => array( 'name' => 'Leads de AfroditaBot' ), 'public' => false, 'show_ui' => false, 'show_in_rest' => false, 'exclude_from_search' => true, 'supports' => array( 'title', 'editor', 'custom-fields' ), ) ); } add_action( 'init', 'afroiabo_register_lead_post_type' ); function afroiabo_leads_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'No tienes permisos para acceder a esta página.', 'afrodita' ) ); } $leads = get_posts( array( 'post_type' => 'afroiabo_lead', 'post_status' => 'private', 'posts_per_page' => 50, 'paged' => max( 1, absint( $_GET['lead_page'] ?? 1 ) ), 'orderby' => 'date', 'order' => 'DESC', ) ); echo '

Leads de AfroditaBot

'; echo '

Contactos captados desde el asistente. Cada registro conserva la conversación visible en el momento del envío.

'; if ( empty( $leads ) ) { echo '

Todavía no se ha recibido ningún lead.

'; return; } echo ''; foreach ( $leads as $lead ) { $type = get_post_meta( $lead->ID, '_afroiabo_type', true ); $name = get_post_meta( $lead->ID, '_afroiabo_name', true ); $email = get_post_meta( $lead->ID, '_afroiabo_email', true ); $phone = get_post_meta( $lead->ID, '_afroiabo_phone', true ); $transcript = get_post_meta( $lead->ID, '_afroiabo_transcript', true ); echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
FechaAcciónContactoMensaje y conversación
' . esc_html( get_the_date( 'd/m/Y H:i', $lead ) ) . '' . esc_html( afroiabo_lead_type_label( $type ) ) . '' . esc_html( $name ) . '
' . esc_html( $email ) . '
' . esc_html( $phone ) . '
' . esc_html( $lead->post_content ) . ''; if ( $transcript ) { echo '
Ver conversación adjunta
' . esc_html( $transcript ) . '
'; } echo '
'; $lead_page = max( 1, absint( $_GET['lead_page'] ?? 1 ) ); if ( $lead_page > 1 ) { echo 'Anteriores '; } if ( 50 === count( $leads ) ) { echo 'Siguientes'; } echo ''; } function afroiabo_verification_notice_message( $credentials ) { $email = esc_html( sanitize_email( $credentials['email'] ?? '' ) ); if ( ! empty( $credentials['last_error'] ) ) { return 'No se ha podido completar la conexión. Revisa el aviso y vuelve a solicitar el enlace para ' . $email . '.'; } if ( absint( $credentials['verification_requested_at'] ?? 0 ) > time() - 30 * MINUTE_IN_SECONDS ) { return 'Se ha solicitado un enlace de verificación para ' . $email . '. Revisa la bandeja de entrada y el correo no deseado. Si no aparece, puedes volver a solicitarlo.'; } return 'Para activar las sesiones seguras, solicita un enlace y verifica esta instalación desde ' . $email . '.'; } function afroiabo_config_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'No tienes permisos para acceder a esta página.', 'afrodita' ) ); } if ( ! afroiabo_is_local_preview() ) { $credentials = afroiabo_get_installation_credentials(); if ( 'verified' !== ( $credentials['status'] ?? '' ) ) { afroiabo_request_installation_verification( false ); if ( absint( $credentials['requested_at'] ?? 0 ) > 0 ) { afroiabo_refresh_installation_status(); } } } echo '
'; if ( ! afroiabo_is_local_preview() ) { $credentials = afroiabo_get_installation_credentials(); if ( 'verified' !== ( $credentials['status'] ?? '' ) ) { echo '
'; echo '

Protege esta instalación de AfroditaBot

'; echo '

' . afroiabo_verification_notice_message( $credentials ) . '

'; if ( ! empty( $credentials['last_error'] ) ) { echo '

' . esc_html( $credentials['last_error'] ) . '

'; } echo ' '; echo ''; echo '
'; } } $template_path = plugin_dir_path( __FILE__ ) . 'templates/XX2.html'; $runtime_config_json = wp_json_encode( afroiabo_runtime_config(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); printf( '', $runtime_config_json ); echo '
'; if ( file_exists( $template_path ) ) { include $template_path; } else { echo '

Error: no se encontró la plantilla principal.

'; } echo '
'; } function afroiabo_security_script_data( $scope ) { $token = afroiabo_get_backend_token( $scope ); $credentials = afroiabo_get_installation_credentials(); return array( 'backendOrigin' => afroiabo_backend_url(), 'token' => is_wp_error( $token ) ? '' : $token, 'scope' => $scope, 'refreshUrl' => admin_url( 'admin-ajax.php' ), 'refreshNonce' => ( 'admin' === $scope ) ? wp_create_nonce( 'afroiabo_admin_settings' ) : '', 'logoutUrl' => ( 'admin' === $scope ) ? wp_logout_url( admin_url() ) : '', 'installationStatus' => afroiabo_is_local_preview() ? 'verified' : sanitize_key( $credentials['status'] ?? 'pending' ), 'localPreview' => afroiabo_is_local_preview(), ); } function afroiabo_ajax_refresh_token() { nocache_headers(); if ( 'POST' !== ( $_SERVER['REQUEST_METHOD'] ?? '' ) ) { wp_send_json_error( array( 'message' => 'Método no permitido.' ), 405 ); } $scope = 'admin' === ( $_POST['scope'] ?? '' ) ? 'admin' : 'widget'; if ( 'admin' === $scope ) { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Inicia sesión de nuevo en WordPress.' ), 403 ); } check_ajax_referer( 'afroiabo_admin_settings', 'nonce' ); } if ( ! afroiabo_rate_limit( 'session', 40, MINUTE_IN_SECONDS ) ) { wp_send_json_error( array( 'message' => 'Espera un momento antes de renovar la sesión.' ), 429 ); } if ( '1' === ( $_POST['force'] ?? '' ) ) { $credentials = afroiabo_get_installation_credentials(); delete_transient( 'afroiabo_token_' . md5( $scope . '|' . $credentials['installation_id'] ) ); } $token = afroiabo_get_backend_token( $scope ); if ( is_wp_error( $token ) ) { wp_send_json_error( array( 'message' => $token->get_error_message() ), 503 ); } $parts = explode( '.', $token ); $payload = json_decode( base64_decode( strtr( $parts[0], '-_', '+/' ) ), true ); wp_send_json_success( array( 'token' => $token, 'expiresAt' => (int) ( $payload['exp'] ?? 0 ) ) ); } add_action( 'wp_ajax_afroiabo_refresh_token', 'afroiabo_ajax_refresh_token' ); add_action( 'wp_ajax_nopriv_afroiabo_refresh_token', 'afroiabo_ajax_refresh_token' ); function afroiabo_ajax_check_verification() { check_ajax_referer( 'afroiabo_admin_settings', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'No tienes permisos para comprobar la instalación.' ), 403 ); } $result = afroiabo_refresh_installation_status(); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ), 502 ); } if ( 'verified' === ( $result['status'] ?? '' ) ) { wp_send_json_success( array( 'status' => 'verified', 'message' => 'Instalación verificada correctamente.' ) ); } wp_send_json_success( array( 'status' => 'pending', 'message' => 'El enlace aún no se ha confirmado.' ) ); } add_action( 'wp_ajax_afroiabo_check_verification', 'afroiabo_ajax_check_verification' ); function afroiabo_ajax_resend_verification() { check_ajax_referer( 'afroiabo_admin_settings', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'No tienes permisos para reenviar la verificación.' ), 403 ); } $result = afroiabo_request_installation_verification( true ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ), 502 ); } wp_send_json_success( array( 'status' => $result['status'] ?? 'pending', 'message' => 'Correo de verificación enviado.' ) ); } add_action( 'wp_ajax_afroiabo_resend_verification', 'afroiabo_ajax_resend_verification' ); function afroiabo_enqueue_admin_assets( $hook ) { if ( 'toplevel_page_afroiabo_kam_assistant_config' !== $hook ) { return; } wp_enqueue_style( 'afrodiabo-tailwind', plugins_url( 'assets/css/Afrotw.css', __FILE__ ), array(), AFROIABO_VERSION ); wp_enqueue_style( 'afroiabo-dashboard-redesign', plugin_dir_url( __FILE__ ) . 'templates/dashboard-redesign.css', array(), AFROIABO_VERSION ); wp_enqueue_style( 'afroiabo-commercial-preview-style', plugin_dir_url( __FILE__ ) . 'assets/css/chat-widget2.css', array(), AFROIABO_VERSION ); // Cargar en cabecera para proteger las primeras llamadas de los iframes de la consola. wp_enqueue_script( 'afroiabo-backend-auth', plugin_dir_url( __FILE__ ) . 'assets/js/backend-auth.js', array(), AFROIABO_VERSION, false ); wp_localize_script( 'afroiabo-backend-auth', 'afroiaboSecurity', afroiabo_security_script_data( 'admin' ) ); wp_enqueue_script( 'afroiabo-voice-input', plugin_dir_url( __FILE__ ) . 'assets/js/voice-input.js', array( 'afroiabo-backend-auth' ), AFROIABO_VERSION, false ); wp_register_script( 'afroiabo-inline', '', array( 'afroiabo-backend-auth', 'afroiabo-voice-input' ), AFROIABO_VERSION, true ); wp_enqueue_script( 'afroiabo-inline' ); $current_user = wp_get_current_user(); $admin_email = sanitize_email( get_option( 'admin_email' ) ); $site_url = esc_url_raw( get_bloginfo( 'url' ) ); wp_localize_script( 'afroiabo-inline', 'appConfig', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'afroiabo_admin_settings' ), 'currentUsername' => $current_user->user_login, 'currentEmail' => $current_user->user_email, 'adminEmail' => $admin_email, 'siteURL' => $site_url, 'dnaTheme' => afroiabo_get_console_dna_theme(), 'backendUrl' => afroiabo_backend_url(), 'publicNonce' => wp_create_nonce( 'afroiabo_public' ), ) ); wp_enqueue_script( 'afroiabo-dna-theme', plugin_dir_url( __FILE__ ) . 'assets/js/dna-theme.js', array( 'afroiabo-inline' ), AFROIABO_VERSION, true ); wp_enqueue_script( 'afroiabo-commercial-preview', plugin_dir_url( __FILE__ ) . 'assets/js/commercial-actions.js', array( 'afroiabo-inline' ), AFROIABO_VERSION, true ); wp_localize_script( 'afroiabo-commercial-preview', 'afroiaboChatWidgetConfig', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'afroiabo_public' ), ) ); $public_reference = afroiabo_public_user_reference(); $bootstrap = 'localStorage.setItem("adminEmail2", ' . wp_json_encode( $public_reference ) . ');'; $bootstrap .= 'localStorage.setItem("siteUrl", ' . wp_json_encode( $site_url ) . ');'; $bootstrap .= 'localStorage.setItem("token", ' . wp_json_encode( $public_reference ) . ');'; $bootstrap .= 'localStorage.removeItem("userId");'; $bootstrap .= 'Object.entries(' . wp_json_encode( afroiabo_widget_storage_config(), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ').forEach(function(entry){localStorage.setItem(entry[0],entry[1]);});'; if ( afroiabo_is_local_preview() ) { $bootstrap .= 'Object.entries(' . wp_json_encode( afroiabo_preview_defaults( $admin_email ) ) . ').forEach(function(entry){if(localStorage.getItem(entry[0])===null){localStorage.setItem(entry[0],entry[1]);}});'; } // La plantilla principal comprueba estos valores durante su propio renderizado. // Deben existir en cabecera para que una instalación nueva no entre en un bucle // antes de que WordPress imprima los scripts del pie de página. wp_add_inline_script( 'afroiabo-backend-auth', $bootstrap, 'before' ); $inline = <<<'JS' document.addEventListener('click', async function(event) { const button = event.target.closest('[data-afroiabo-verification]'); if (!button || typeof appConfig === 'undefined') return; const feedback = document.getElementById('afroiabo-verification-feedback'); const action = button.dataset.afroiaboVerification === 'resend' ? 'afroiabo_resend_verification' : 'afroiabo_check_verification'; button.disabled = true; if (feedback) feedback.textContent = 'Comprobando…'; try { const payload = new URLSearchParams({ action: action, nonce: appConfig.nonce }); const response = await fetch(appConfig.ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: payload.toString() }); const result = await response.json(); const data = result && result.data ? result.data : {}; if (!response.ok || !result.success) throw new Error(data.message || 'No se pudo completar la comprobación.'); if (feedback) feedback.textContent = data.message || 'Comprobación completada.'; if (data.status === 'verified') window.location.reload(); } catch (error) { if (feedback) feedback.textContent = error.message; } finally { button.disabled = false; } }); JS; wp_add_inline_script( 'afroiabo-inline', $inline ); if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all_plugins = get_plugins(); $active = get_option( 'active_plugins', array() ); $detail = array(); foreach ( $all_plugins as $plugin_file => $info ) { $detail[ $plugin_file ] = array( 'Name' => isset( $info['Name'] ) ? $info['Name'] : $plugin_file, 'Active' => in_array( $plugin_file, $active, true ), ); } wp_localize_script( 'afroiabo-inline', 'afroditaPlugins', $detail ); } add_action( 'admin_enqueue_scripts', 'afroiabo_enqueue_admin_assets' ); function afroiabo_enqueue_assets() { // Prevent a second assistant inside its own booking panel. if ( isset( $_GET['afroiabo_booking_embed'] ) && '1' === $_GET['afroiabo_booking_embed'] ) { return; } wp_enqueue_style( 'afroiabo-style', plugin_dir_url( __FILE__ ) . 'assets/css/chat-widget2.css', array(), AFROIABO_VERSION ); wp_enqueue_script( 'afroiabo-backend-auth', plugin_dir_url( __FILE__ ) . 'assets/js/backend-auth.js', array(), AFROIABO_VERSION, true ); wp_localize_script( 'afroiabo-backend-auth', 'afroiaboSecurity', afroiabo_security_script_data( 'widget' ) ); wp_enqueue_script( 'afroiabo-voice-input', plugin_dir_url( __FILE__ ) . 'assets/js/voice-input.js', array( 'afroiabo-backend-auth' ), AFROIABO_VERSION, true ); wp_enqueue_script( 'afroiabo-booking-conversation', plugin_dir_url( __FILE__ ) . 'assets/js/conversational-booking.js', array(), AFROIABO_VERSION . '-' . filemtime( __DIR__ . '/assets/js/conversational-booking.js' ), true ); wp_enqueue_script( 'afroiabo-script', plugin_dir_url( __FILE__ ) . 'assets/js/chat-widget2.js', array( 'afroiabo-backend-auth', 'afroiabo-voice-input', 'afroiabo-booking-conversation' ), AFROIABO_VERSION . '-' . filemtime( __DIR__ . '/assets/js/chat-widget2.js' ), true ); wp_enqueue_script( 'afroiabo-commercial-actions', plugin_dir_url( __FILE__ ) . 'assets/js/commercial-actions.js', array( 'afroiabo-script' ), AFROIABO_VERSION, true ); wp_localize_script( 'afroiabo-script', 'afroiaboChatWidgetConfig', array( 'SYSTEM_ID' => afroiabo_public_user_reference(), 'CONFIG_URL' => afroiabo_backend_url() . '/api/get-config2', 'SSE_ENDPOINT' => afroiabo_backend_url() . '/nuevoEndpointKamelot12WP', 'TRANSCRIPTION_URL' => afroiabo_backend_url() . '/api/transcribe', 'MAX_HISTORY' => 50, 'BOOKING' => afroiabo_conversational_booking_config(), 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'afroiabo_public' ), ) ); } add_action( 'wp_enqueue_scripts', 'afroiabo_enqueue_assets' ); function afroiabo_widget_option_key() { return 'afroiabo_widget_config'; } function afroiabo_commercial_option_key() { return 'afroiabo_commercial_config'; } /** Pilot explicitly enabled per installation. Never expose administrator REST credentials. */ function afroiabo_conversational_booking_config() { $pilot = get_option( 'afroiabo_conversational_booking', array() ); $commercial = afroiabo_get_commercial_config(); if ( empty( $pilot['enabled'] ) || empty( $pilot['service_id'] ) || empty( $commercial['enabled'] ) || empty( $commercial['booking_enabled'] ) || 'ssa' !== $commercial['booking_provider'] || ! afroiabo_booking_provider_active( 'ssa' ) || ! class_exists( 'TD_API_Model' ) ) { return null; } return array( 'url' => rest_url( 'ssa/v1/' ), 'nonce' => TD_API_Model::create_nonce( 'wp_rest' ), 'serviceId' => absint( $pilot['service_id'] ), 'zone' => wp_timezone_string(), 'interpretUrl' => ! empty( $pilot['ai_enabled'] ) ? afroiabo_backend_url() . '/api/booking-intent' : null ); } function afroiabo_default_commercial_config() { return array( 'enabled' => true, 'cta_title' => '¿Quieres dar el siguiente paso?', 'cta_text' => 'Elige cómo prefieres continuar.', 'email_enabled' => true, 'email_to' => sanitize_email( get_option( 'admin_email' ) ), 'whatsapp_enabled' => false, 'whatsapp_phone' => '', 'whatsapp_message' => 'Hola, vengo desde el asistente de la web y me gustaría continuar la conversación.', 'booking_enabled' => false, 'booking_provider' => 'calendly', 'booking_url' => '', 'booking_display' => 'page', 'call_enabled' => true, 'lead_enabled' => true, 'woocommerce_catalog_enabled'=> true, 'woocommerce_orders_enabled' => true, 'privacy_url' => esc_url_raw( get_privacy_policy_url() ), ); } function afroiabo_get_commercial_config() { $config = get_option( afroiabo_commercial_option_key(), array() ); if ( ! is_array( $config ) ) { $config = array(); } return wp_parse_args( $config, afroiabo_default_commercial_config() ); } function afroiabo_configuration_profiles_option_key() { return 'afroiabo_configuration_profiles'; } function afroiabo_configuration_profiles_state() { $state = get_option( afroiabo_configuration_profiles_option_key(), array() ); if ( ! is_array( $state ) ) { $state = array(); } $profiles = isset( $state['profiles'] ) && is_array( $state['profiles'] ) ? $state['profiles'] : array(); return array( 'version' => 1, 'active' => sanitize_text_field( $state['active'] ?? '' ), 'profiles' => $profiles, ); } function afroiabo_profile_text( $value, $limit ) { $value = sanitize_textarea_field( (string) $value ); if ( function_exists( 'mb_substr' ) ) { return mb_substr( $value, 0, $limit ); } return substr( $value, 0, $limit ); } function afroiabo_profile_boolean( $value ) { return true === $value || 1 === $value || in_array( strtolower( (string) $value ), array( '1', 'true', 'on' ), true ); } function afroiabo_sanitize_profile_widget( $raw ) { $raw = is_array( $raw ) ? $raw : array(); $defaults = afroiabo_default_widget_config(); $height = max( 1, min( 2000, absint( $raw['button_background_height'] ?? $raw['button_background_size'] ?? $defaults['button_background_height'] ) ) ); $width = max( 1, min( 2000, absint( $raw['button_background_width'] ?? round( $height * 4.2 ) ) ) ); $icon = sanitize_key( $raw['button_icon'] ?? $defaults['button_icon'] ); $size = sanitize_key( $raw['button_size'] ?? $defaults['button_size'] ); $variant = sanitize_key( $raw['button_variant'] ?? $defaults['button_variant'] ); $layout = sanitize_key( $raw['button_layout'] ?? $defaults['button_layout'] ); $icon_style = sanitize_key( $raw['button_icon_style'] ?? $defaults['button_icon_style'] ); $position = sanitize_key( $raw['button_position'] ?? $defaults['button_position'] ); return array( 'primary_color' => sanitize_hex_color( $raw['primary_color'] ?? '' ) ?: $defaults['primary_color'], 'button_text' => sanitize_text_field( $raw['button_text'] ?? '' ), 'button_icon' => in_array( $icon, array( 'chat', 'sparkles', 'bot', 'headset', 'bolt', 'heart', 'custom' ), true ) ? $icon : 'chat', 'button_icon_image' => esc_url_raw( $raw['button_icon_image'] ?? '', array( 'http', 'https' ) ), 'button_size' => in_array( $size, array( 'sm', 'md', 'lg' ), true ) ? $size : 'md', 'button_variant' => in_array( $variant, array( 'solid', 'outline', 'ghost' ), true ) ? $variant : 'solid', 'button_layout' => in_array( $layout, array( 'pill', 'icon', 'text' ), true ) ? $layout : 'pill', 'button_icon_style' => in_array( $icon_style, array( 'plain', 'soft', 'contrast' ), true ) ? $icon_style : 'plain', 'button_icon_size' => max( 12, min( 48, absint( $raw['button_icon_size'] ?? $defaults['button_icon_size'] ) ) ), 'button_icon_offset_x' => max( -1000, min( 1000, intval( $raw['button_icon_offset_x'] ?? 0 ) ) ), 'button_text_size' => max( 10, min( 24, absint( $raw['button_text_size'] ?? $defaults['button_text_size'] ) ) ), 'button_background_size' => $height, 'button_background_width'=> $width, 'button_background_height'=> $height, 'button_position' => in_array( $position, array( 'right', 'left' ), true ) ? $position : 'right', 'button_border_radius' => max( 0, min( 50, absint( $raw['button_border_radius'] ?? $defaults['button_border_radius'] ) ) ), 'button_shadow' => afroiabo_profile_boolean( $raw['button_shadow'] ?? false ), 'button_animation' => afroiabo_profile_boolean( $raw['button_animation'] ?? false ), 'button_disabled' => afroiabo_profile_boolean( $raw['button_disabled'] ?? false ), ); } function afroiabo_sanitize_profile_commercial( $raw ) { $raw = is_array( $raw ) ? $raw : array(); $defaults = afroiabo_default_commercial_config(); $provider = sanitize_key( $raw['booking_provider'] ?? $defaults['booking_provider'] ); if ( ! afroiabo_booking_provider_valid( $provider ) ) { $provider = $defaults['booking_provider']; } $email = sanitize_email( $raw['email_to'] ?? $defaults['email_to'] ); if ( ! is_email( $email ) ) { $email = sanitize_email( get_option( 'admin_email' ) ); } return array( 'enabled' => afroiabo_profile_boolean( $raw['enabled'] ?? false ), 'cta_title' => sanitize_text_field( $raw['cta_title'] ?? $defaults['cta_title'] ), 'cta_text' => sanitize_text_field( $raw['cta_text'] ?? $defaults['cta_text'] ), 'email_enabled' => afroiabo_profile_boolean( $raw['email_enabled'] ?? false ), 'email_to' => $email, 'whatsapp_enabled' => afroiabo_profile_boolean( $raw['whatsapp_enabled'] ?? false ), 'whatsapp_phone' => afroiabo_sanitize_phone( $raw['whatsapp_phone'] ?? '' ), 'whatsapp_message' => sanitize_textarea_field( $raw['whatsapp_message'] ?? $defaults['whatsapp_message'] ), 'booking_enabled' => afroiabo_profile_boolean( $raw['booking_enabled'] ?? false ), 'booking_provider' => $provider, 'booking_url' => afroiabo_safe_external_url( $raw['booking_url'] ?? '' ), 'booking_display' => 'embedded' === sanitize_key( $raw['booking_display'] ?? '' ) ? 'embedded' : 'page', 'call_enabled' => afroiabo_profile_boolean( $raw['call_enabled'] ?? false ), 'lead_enabled' => afroiabo_profile_boolean( $raw['lead_enabled'] ?? false ), 'woocommerce_catalog_enabled' => afroiabo_profile_boolean( $raw['woocommerce_catalog_enabled'] ?? false ), 'woocommerce_orders_enabled' => afroiabo_profile_boolean( $raw['woocommerce_orders_enabled'] ?? false ), 'privacy_url' => afroiabo_safe_external_url( $raw['privacy_url'] ?? '' ), ); } function afroiabo_sanitize_profile_console_theme( $raw, $design, $profile_name = '' ) { $raw = is_array( $raw ) ? $raw : array(); $design = is_array( $design ) ? $design : afroiabo_default_widget_config(); $primary = sanitize_hex_color( $raw['primary_color'] ?? '' ) ?: ( sanitize_hex_color( $design['primary_color'] ?? '' ) ?: '#6d3df5' ); $secondary = sanitize_hex_color( $raw['secondary_color'] ?? '' ) ?: $primary; $fallback_radius = max( 14, min( 32, (int) round( absint( $design['button_border_radius'] ?? 26 ) * 0.55 ) ) ); $site_name = sanitize_text_field( $raw['site_name'] ?? '' ); if ( '' === $site_name ) { $site_name = sanitize_text_field( preg_replace( '/\s*[·-]\s*ADN\s*$/iu', '', (string) $profile_name ) ); } if ( '' === $site_name ) { $site_name = sanitize_text_field( get_bloginfo( 'name' ) ); } $source_url = esc_url_raw( $raw['source_url'] ?? '', array( 'http', 'https' ) ); if ( ! $source_url ) { $source_url = esc_url_raw( home_url( '/' ), array( 'http', 'https' ) ); } $logo_url = esc_url_raw( $raw['logo_url'] ?? '', array( 'http', 'https' ) ); if ( ! $logo_url ) { $logo_url = esc_url_raw( $design['button_icon_image'] ?? '', array( 'http', 'https' ) ); } return array( 'enabled' => true, 'primary_color' => $primary, 'secondary_color' => $secondary, 'border_radius' => max( 14, min( 32, absint( $raw['border_radius'] ?? $fallback_radius ) ) ), 'site_name' => $site_name, 'source_url' => $source_url, 'logo_url' => $logo_url, ); } function afroiabo_sanitize_knowledge_documents( $documents ) { $result = array(); foreach ( array_slice( is_array( $documents ) ? $documents : array(), 0, 11 ) as $document ) { if ( ! is_array( $document ) ) { continue; } $url = esc_url_raw( $document['url'] ?? '', array( 'http', 'https' ) ); $text = afroiabo_profile_text( $document['text'] ?? '', 12000 ); if ( $url && $text ) { $result[] = array( 'url' => $url, 'text' => $text ); } } return $result; } function afroiabo_sanitize_profile_snapshot( $raw, $profile_name = '' ) { $raw = is_array( $raw ) ? $raw : array(); $responses = isset( $raw['responses'] ) && is_array( $raw['responses'] ) ? $raw['responses'] : array(); $design = afroiabo_sanitize_profile_widget( $raw['design'] ?? array() ); return array( 'responses' => array( 'knowledgeDocuments' => afroiabo_sanitize_knowledge_documents( $responses['knowledgeDocuments'] ?? array() ), 'systemMessage2' => afroiabo_profile_text( $responses['systemMessage2'] ?? '', 250000 ), 'eventosk' => afroiabo_profile_text( $responses['eventosk'] ?? '', 50000 ), 'Pregunta1' => afroiabo_profile_text( $responses['Pregunta1'] ?? '', 1000 ), 'MManual' => afroiabo_profile_text( $responses['MManual'] ?? '', 5000 ), 'MoP' => 1 === absint( $responses['MoP'] ?? 2 ) ? 1 : 2, 'tono' => afroiabo_profile_text( $responses['tono'] ?? '', 1000 ), 'Modelo' => '12', ), 'design' => $design, 'theme' => afroiabo_sanitize_profile_console_theme( $raw['theme'] ?? array(), $design, $profile_name ), 'commercial' => afroiabo_sanitize_profile_commercial( $raw['commercial'] ?? array() ), ); } function afroiabo_posted_boolean( $key ) { return isset( $_POST[ $key ] ) && in_array( (string) wp_unslash( $_POST[ $key ] ), array( '1', 'true', 'on' ), true ); } function afroiabo_sanitize_phone( $phone ) { $phone = preg_replace( '/[^0-9+]/', '', (string) $phone ); if ( substr_count( $phone, '+' ) > 1 || ( false !== strpos( $phone, '+' ) && 0 !== strpos( $phone, '+' ) ) ) { $phone = str_replace( '+', '', $phone ); } return substr( $phone, 0, 20 ); } function afroiabo_safe_external_url( $url ) { $url = esc_url_raw( wp_unslash( $url ), array( 'https' ) ); return $url && wp_http_validate_url( $url ) ? $url : ''; } function afroiabo_public_commercial_config() { $config = afroiabo_get_commercial_config(); $booking = afroiabo_booking_connection( $config['booking_provider'], $config['booking_url'], $config['booking_display'] ); $booking_ready = ! is_wp_error( $booking ); $woocommerce_available = class_exists( 'WooCommerce' ) && function_exists( 'wc_get_product' ); return array( 'enabled' => (bool) $config['enabled'], 'ctaTitle' => sanitize_text_field( $config['cta_title'] ), 'ctaText' => sanitize_text_field( $config['cta_text'] ), 'emailEnabled' => (bool) $config['email_enabled'], 'whatsappEnabled' => (bool) $config['whatsapp_enabled'] && ! empty( $config['whatsapp_phone'] ), 'whatsappPhone' => afroiabo_sanitize_phone( $config['whatsapp_phone'] ), 'whatsappMessage' => sanitize_textarea_field( $config['whatsapp_message'] ), 'bookingEnabled' => (bool) $config['booking_enabled'] && $booking_ready, 'bookingProvider' => $booking_ready ? $booking['provider'] : '', 'bookingUrl' => $booking_ready ? $booking['url'] : '', 'bookingDisplay' => $booking_ready ? $booking['display'] : 'page', 'callEnabled' => (bool) $config['call_enabled'], 'leadEnabled' => (bool) $config['lead_enabled'], 'catalogEnabled' => $woocommerce_available && (bool) $config['woocommerce_catalog_enabled'], 'ordersEnabled' => $woocommerce_available && (bool) $config['woocommerce_orders_enabled'], 'woocommerceAvailable' => $woocommerce_available, 'privacyUrl' => esc_url_raw( $config['privacy_url'] ), ); } function afroiabo_default_widget_config() { return array( 'primary_color' => '#000000', 'button_text' => '¿Hablamos?', 'button_icon' => 'chat', 'button_icon_image' => '', 'button_size' => 'md', 'button_variant' => 'solid', 'button_layout' => 'pill', 'button_icon_style' => 'plain', 'button_icon_size' => 20, 'button_icon_offset_x' => 0, 'button_text_size' => 14, 'button_background_size'=> 46, 'button_background_width' => 193, 'button_background_height'=> 46, 'button_position' => 'right', 'button_border_radius' => 50, 'button_shadow' => true, 'button_animation' => true, 'button_disabled' => false, ); } function afroiabo_get_widget_config() { $saved = get_option( afroiabo_widget_option_key(), array() ); if ( ! is_array( $saved ) ) { $saved = array(); } $config = wp_parse_args( $saved, afroiabo_default_widget_config() ); $legacy_height = max( 1, absint( $saved['button_background_size'] ?? $config['button_background_height'] ) ); if ( ! array_key_exists( 'button_background_height', $saved ) ) { $config['button_background_height'] = $legacy_height; } if ( ! array_key_exists( 'button_background_width', $saved ) ) { $config['button_background_width'] = max( 1, (int) round( $legacy_height * 4.2 ) ); } return $config; } function afroiabo_widget_storage_config() { $config = afroiabo_get_widget_config(); return array( 'kamelot-primary-color' => sanitize_hex_color( $config['primary_color'] ) ?: '#000000', 'kamelot-button-text' => sanitize_text_field( $config['button_text'] ), 'kamelot-button-icon' => sanitize_key( $config['button_icon'] ), 'kamelot-button-icon-image' => esc_url_raw( $config['button_icon_image'], array( 'http', 'https' ) ), 'kamelot-button-size' => sanitize_key( $config['button_size'] ), 'kamelot-button-variant' => sanitize_key( $config['button_variant'] ), 'kamelot-button-layout' => sanitize_key( $config['button_layout'] ), 'kamelot-button-icon-style' => sanitize_key( $config['button_icon_style'] ), 'kamelot-button-icon-size' => (string) absint( $config['button_icon_size'] ), 'kamelot-button-icon-offset-x' => (string) max( -1000, min( 1000, intval( $config['button_icon_offset_x'] ) ) ), 'kamelot-button-text-size' => (string) absint( $config['button_text_size'] ), 'kamelot-button-background-size' => (string) absint( $config['button_background_size'] ), 'kamelot-button-background-width' => (string) absint( $config['button_background_width'] ), 'kamelot-button-background-height'=> (string) absint( $config['button_background_height'] ), 'kamelot-button-position' => sanitize_key( $config['button_position'] ), 'kamelot-button-border-radius' => (string) absint( $config['button_border_radius'] ), 'kamelot-button-shadow' => ! empty( $config['button_shadow'] ) ? '1' : '0', 'kamelot-button-animation' => ! empty( $config['button_animation'] ) ? '1' : '0', 'kamelot-button-disabled' => ! empty( $config['button_disabled'] ) ? '1' : '0', ); } function afroiabo_console_dna_theme_option_key() { return 'afroiabo_console_dna_theme'; } function afroiabo_default_console_dna_theme() { return array( 'enabled' => false, 'primary_color' => '#6d3df5', 'secondary_color' => '#d83e8d', 'border_radius' => 26, 'site_name' => '', 'source_url' => '', 'logo_url' => '', ); } function afroiabo_get_console_dna_theme() { $theme = get_option( afroiabo_console_dna_theme_option_key(), array() ); if ( ! is_array( $theme ) ) { $theme = array(); } return wp_parse_args( $theme, afroiabo_default_console_dna_theme() ); } function afroiabo_ajax_save_console_dna_theme() { check_ajax_referer( 'afroiabo_admin_settings', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'No tienes permisos para personalizar la consola.' ), 403 ); } $source_url = esc_url_raw( wp_unslash( $_POST['source_url'] ?? '' ), array( 'http', 'https' ) ); if ( ! $source_url || ! wp_http_validate_url( $source_url ) ) { wp_send_json_error( array( 'message' => 'No se ha podido guardar el origen visual de la consola.' ), 400 ); } $theme = array( 'enabled' => true, 'primary_color' => sanitize_hex_color( wp_unslash( $_POST['primary_color'] ?? '' ) ) ?: '#6d3df5', 'secondary_color' => sanitize_hex_color( wp_unslash( $_POST['secondary_color'] ?? '' ) ) ?: '#d83e8d', 'border_radius' => max( 14, min( 32, absint( wp_unslash( $_POST['border_radius'] ?? '26' ) ) ) ), 'site_name' => sanitize_text_field( wp_unslash( $_POST['site_name'] ?? '' ) ), 'source_url' => $source_url, 'logo_url' => esc_url_raw( wp_unslash( $_POST['logo_url'] ?? '' ), array( 'http', 'https' ) ), ); update_option( afroiabo_console_dna_theme_option_key(), $theme, false ); wp_send_json_success( array( 'message' => 'La consola ya comparte la identidad visual de la web.', 'theme' => $theme ) ); } add_action( 'wp_ajax_afroiabo_save_console_dna_theme', 'afroiabo_ajax_save_console_dna_theme' ); function afroiabo_dna_normalize_color( $color ) { $color = strtolower( trim( (string) $color ) ); if ( '' === $color || 'transparent' === $color || 'currentcolor' === $color ) { return ''; } if ( preg_match( '/^#([0-9a-f]{3})$/', $color, $matches ) ) { return '#' . $matches[1][0] . $matches[1][0] . $matches[1][1] . $matches[1][1] . $matches[1][2] . $matches[1][2]; } if ( preg_match( '/^#([0-9a-f]{6})(?:[0-9a-f]{2})?$/', $color, $matches ) ) { return '#' . $matches[1]; } if ( preg_match( '/^rgba?\(\s*(\d{1,3})\s*[, ]\s*(\d{1,3})\s*[, ]\s*(\d{1,3})(?:\s*[,\/]\s*([0-9.]+%?))?/i', $color, $matches ) ) { if ( isset( $matches[4] ) && '' !== $matches[4] ) { $alpha = false !== strpos( $matches[4], '%' ) ? (float) $matches[4] / 100 : (float) $matches[4]; if ( $alpha < 0.08 ) { return ''; } } return sprintf( '#%02x%02x%02x', min( 255, (int) $matches[1] ), min( 255, (int) $matches[2] ), min( 255, (int) $matches[3] ) ); } if ( preg_match( '/^hsla?\(\s*(-?[0-9.]+)(?:deg)?\s*[, ]\s*([0-9.]+)%\s*[, ]\s*([0-9.]+)%/i', $color, $matches ) ) { $hue = fmod( (float) $matches[1], 360 ); $hue = ( $hue < 0 ? $hue + 360 : $hue ) / 360; $saturation = max( 0, min( 1, (float) $matches[2] / 100 ) ); $lightness = max( 0, min( 1, (float) $matches[3] / 100 ) ); if ( 0.0 === $saturation ) { $channel = (int) round( $lightness * 255 ); return sprintf( '#%02x%02x%02x', $channel, $channel, $channel ); } $q = $lightness < 0.5 ? $lightness * ( 1 + $saturation ) : $lightness + $saturation - ( $lightness * $saturation ); $p = ( 2 * $lightness ) - $q; $to_channel = static function ( $position ) use ( $p, $q ) { if ( $position < 0 ) { $position += 1; } if ( $position > 1 ) { $position -= 1; } if ( $position < 1 / 6 ) { return $p + ( ( $q - $p ) * 6 * $position ); } if ( $position < 1 / 2 ) { return $q; } if ( $position < 2 / 3 ) { return $p + ( ( $q - $p ) * ( 2 / 3 - $position ) * 6 ); } return $p; }; return sprintf( '#%02x%02x%02x', (int) round( $to_channel( $hue + 1 / 3 ) * 255 ), (int) round( $to_channel( $hue ) * 255 ), (int) round( $to_channel( $hue - 1 / 3 ) * 255 ) ); } return ''; } function afroiabo_dna_color_metrics( $color ) { $red = hexdec( substr( $color, 1, 2 ) ) / 255; $green = hexdec( substr( $color, 3, 2 ) ) / 255; $blue = hexdec( substr( $color, 5, 2 ) ) / 255; $max = max( $red, $green, $blue ); $min = min( $red, $green, $blue ); return array( 'luminance' => ( 0.2126 * $red ) + ( 0.7152 * $green ) + ( 0.0722 * $blue ), 'saturation'=> $max > 0 ? ( $max - $min ) / $max : 0, ); } function afroiabo_dna_add_colors( &$counts, $source, $boost = 1 ) { if ( ! is_string( $source ) || '' === $source ) { return; } preg_match_all( '/#[0-9a-f]{3,8}\b|rgba?\([^\)]{5,48}\)|hsla?\([^\)]{5,64}\)/i', $source, $matches ); $local_counts = array(); foreach ( $matches[0] as $candidate ) { $color = afroiabo_dna_normalize_color( $candidate ); if ( $color ) { $local_counts[ $color ] = ( $local_counts[ $color ] ?? 0 ) + 1; } } foreach ( $local_counts as $color => $frequency ) { // Evita que un framework gane por repetir cientos de veces un color auxiliar. $counts[ $color ] = ( $counts[ $color ] ?? 0 ) + ( min( 14, $frequency ) * $boost ); } } function afroiabo_dna_add_semantic_colors( &$counts, $source ) { if ( ! is_string( $source ) || '' === $source ) { return; } $color_pattern = '(#[0-9a-f]{3,8}\b|rgba?\([^\)]{5,48}\)|hsla?\([^\)]{5,64}\))'; $add_matches = static function ( $matches, $boost ) use ( &$counts ) { foreach ( $matches as $candidate ) { $normalized = afroiabo_dna_normalize_color( is_array( $candidate ) ? end( $candidate ) : $candidate ); if ( $normalized ) { $counts[ $normalized ] = ( $counts[ $normalized ] ?? 0 ) + $boost; } } }; preg_match_all( '/--[a-z0-9_-]*(?:brand|primary|accent|secondary|cta|button|link)[a-z0-9_-]*\s*:\s*' . $color_pattern . '/i', $source, $variable_matches, PREG_SET_ORDER ); $add_matches( $variable_matches, 34 ); preg_match_all( '/(?:theme-color|msapplication-navbutton-color)[^>]{0,120}(?:content\s*=\s*["\']|:\s*)' . $color_pattern . '/i', $source, $theme_matches, PREG_SET_ORDER ); $add_matches( $theme_matches, 52 ); preg_match_all( '/(?:button|\.btn|\.cta|header|nav|hero|brand|logo|primary|accent)[^{]{0,140}\{[^}]{0,900}\}/i', $source, $blocks ); foreach ( array_slice( $blocks[0] ?? array(), 0, 80 ) as $block ) { preg_match_all( '/(?:background(?:-color)?|border(?:-color)?|color)\s*:\s*' . $color_pattern . '/i', $block, $property_matches, PREG_SET_ORDER ); $add_matches( $property_matches, 13 ); } } function afroiabo_dna_color_distance( $left, $right ) { $left_red = hexdec( substr( $left, 1, 2 ) ); $left_green = hexdec( substr( $left, 3, 2 ) ); $left_blue = hexdec( substr( $left, 5, 2 ) ); $right_red = hexdec( substr( $right, 1, 2 ) ); $right_green = hexdec( substr( $right, 3, 2 ) ); $right_blue = hexdec( substr( $right, 5, 2 ) ); return sqrt( ( ( $left_red - $right_red ) ** 2 ) + ( ( $left_green - $right_green ) ** 2 ) + ( ( $left_blue - $right_blue ) ** 2 ) ); } function afroiabo_dna_pick_palette( $counts ) { $ranked = array(); foreach ( $counts as $color => $frequency ) { $metrics = afroiabo_dna_color_metrics( $color ); if ( $metrics['luminance'] < 0.025 || $metrics['luminance'] > 0.95 ) { continue; } $saturation_factor = 0.25 + ( 1.75 * $metrics['saturation'] ); if ( $metrics['saturation'] < 0.08 ) { $saturation_factor *= 0.16; } elseif ( $metrics['saturation'] < 0.18 ) { $saturation_factor *= 0.48; } $ranked[ $color ] = $frequency * $saturation_factor; } arsort( $ranked ); $palette = array(); foreach ( array_keys( $ranked ) as $color ) { $too_similar = false; foreach ( $palette as $selected ) { if ( afroiabo_dna_color_distance( $color, $selected ) < 46 ) { $too_similar = true; break; } } if ( ! $too_similar ) { $palette[] = $color; } if ( 5 === count( $palette ) ) { break; } } if ( empty( $palette ) ) { $palette = array( '#26232b' ); } return $palette; } function afroiabo_dna_resolve_url( $url, $base_url ) { $url = trim( html_entity_decode( (string) $url, ENT_QUOTES, 'UTF-8' ) ); if ( wp_http_validate_url( $url ) ) { return $url; } $base = wp_parse_url( $base_url ); if ( empty( $base['scheme'] ) || empty( $base['host'] ) ) { return ''; } if ( 0 === strpos( $url, '//' ) ) { return wp_http_validate_url( $base['scheme'] . ':' . $url ) ?: ''; } $origin = $base['scheme'] . '://' . $base['host'] . ( isset( $base['port'] ) ? ':' . (int) $base['port'] : '' ); if ( 0 === strpos( $url, '/' ) ) { return wp_http_validate_url( $origin . $url ) ?: ''; } $path = $base['path'] ?? '/'; $directory = trailingslashit( preg_replace( '#/[^/]*$#', '', $path ) ); return wp_http_validate_url( $origin . $directory . $url ) ?: ''; } function afroiabo_dna_tag_attribute( $tag, $attribute ) { $attribute_pattern = preg_quote( $attribute, '/' ); if ( preg_match( '/\b' . $attribute_pattern . '\s*=\s*(["\'])(.*?)\1/is', (string) $tag, $matches ) ) { return html_entity_decode( trim( $matches[2] ), ENT_QUOTES, 'UTF-8' ); } if ( preg_match( '/\b' . $attribute_pattern . '\s*=\s*([^\s>]+)/i', (string) $tag, $matches ) ) { return html_entity_decode( trim( $matches[1], "\"'" ), ENT_QUOTES, 'UTF-8' ); } return ''; } function afroiabo_dna_image_source_from_tag( $tag, $base_url ) { foreach ( array( 'data-src', 'data-lazy-src', 'data-original', 'src' ) as $attribute ) { $candidate = afroiabo_dna_tag_attribute( $tag, $attribute ); $resolved = afroiabo_dna_resolve_url( $candidate, $base_url ); if ( $resolved ) { return $resolved; } } foreach ( array( 'data-srcset', 'srcset' ) as $attribute ) { $srcset = afroiabo_dna_tag_attribute( $tag, $attribute ); if ( ! $srcset ) { continue; } $sources = array_reverse( array_filter( array_map( 'trim', explode( ',', $srcset ) ) ) ); foreach ( $sources as $source ) { $candidate = preg_split( '/\s+/', $source )[0] ?? ''; $resolved = afroiabo_dna_resolve_url( $candidate, $base_url ); if ( $resolved ) { return $resolved; } } } return ''; } function afroiabo_dna_find_logo( $html, $site_url ) { $best = array( 'url' => '', 'source' => '', 'width' => 0, 'height' => 0, 'score' => 0, ); $site_scheme = wp_parse_url( $site_url, PHP_URL_SCHEME ); $add_candidate = static function ( $candidate_url, $score, $source, $width = 0, $height = 0 ) use ( &$best, $site_url, $site_scheme ) { $candidate_url = str_replace( '\\/', '/', (string) $candidate_url ); $resolved = afroiabo_dna_resolve_url( $candidate_url, $site_url ); if ( ! $resolved || ( 'https' === $site_scheme && 'http' === wp_parse_url( $resolved, PHP_URL_SCHEME ) ) ) { return; } $asset_path = strtolower( wp_parse_url( $resolved, PHP_URL_PATH ) ?: '' ); if ( preg_match( '/(?:spacer|pixel|tracking|blank|avatar-default)/', $asset_path ) ) { return; } $has_logo_name = false !== stripos( $asset_path, 'logo' ) || false !== stripos( $asset_path, 'brand' ); if ( $has_logo_name ) { $score += 24; } if ( $width >= 96 || $height >= 96 ) { $score += 5; } if ( $width > 0 && $height > 0 ) { $ratio = $width / $height; if ( $ratio > 7 || $ratio < 0.14 ) { $score -= 22; } if ( 'social-image' === $source && ! $has_logo_name && ( $ratio > 1.45 || $ratio < 0.68 ) ) { $score -= 30; } } if ( 'social-image' === $source && ! $has_logo_name ) { $score -= 14; if ( ! $width || ! $height ) { $score -= 26; } } if ( $score > $best['score'] ) { $best = array( 'url' => esc_url_raw( $resolved, array( 'http', 'https' ) ), 'source' => sanitize_key( $source ), 'width' => absint( $width ), 'height' => absint( $height ), 'score' => absint( $score ), ); } }; if ( preg_match_all( '/]*type=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/is', $html, $json_blocks ) ) { foreach ( $json_blocks[1] as $json_block ) { if ( preg_match_all( '/["\']logo["\']\s*:\s*(?:["\']([^"\']+)["\']|\{[^\}]{0,1000}?["\']url["\']\s*:\s*["\']([^"\']+)["\'])/is', $json_block, $logo_matches, PREG_SET_ORDER ) ) { foreach ( $logo_matches as $logo_match ) { $add_candidate( $logo_match[1] ?: ( $logo_match[2] ?? '' ), 115, 'structured-data' ); } } } } if ( preg_match_all( '/]*>/i', $html, $meta_tags ) ) { $social_width = 0; $social_height = 0; $social_url = ''; foreach ( $meta_tags[0] as $tag ) { $key = strtolower( afroiabo_dna_tag_attribute( $tag, 'property' ) ?: afroiabo_dna_tag_attribute( $tag, 'name' ) ?: afroiabo_dna_tag_attribute( $tag, 'itemprop' ) ); $content = afroiabo_dna_tag_attribute( $tag, 'content' ); if ( 'og:image:width' === $key ) { $social_width = absint( $content ); } elseif ( 'og:image:height' === $key ) { $social_height = absint( $content ); } elseif ( false !== strpos( $key, 'logo' ) ) { $add_candidate( $content, 105, 'metadata-logo' ); } elseif ( 'msapplication-tileimage' === $key ) { $add_candidate( $content, 42, 'site-icon' ); } elseif ( in_array( $key, array( 'og:image', 'og:image:secure_url', 'twitter:image' ), true ) && ! $social_url ) { $social_url = $content; } } if ( $social_url ) { $add_candidate( $social_url, 58, 'social-image', $social_width, $social_height ); } } if ( preg_match_all( '/]*>/i', $html, $image_tags ) ) { foreach ( $image_tags[0] as $tag ) { $identity = strtolower( implode( ' ', array( afroiabo_dna_tag_attribute( $tag, 'id' ), afroiabo_dna_tag_attribute( $tag, 'class' ), afroiabo_dna_tag_attribute( $tag, 'alt' ), afroiabo_dna_tag_attribute( $tag, 'aria-label' ), afroiabo_dna_tag_attribute( $tag, 'itemprop' ), ) ) ); if ( ! preg_match( '/(?:^|[\s_-])(logo|brand|marca)(?:$|[\s_-])/i', $identity ) ) { continue; } $score = 78; if ( preg_match( '/(?:custom|site|header)[\s_-]*logo|logo[\s_-]*(?:custom|site|header)/i', $identity ) ) { $score += 25; } $add_candidate( afroiabo_dna_image_source_from_tag( $tag, $site_url ), $score, 'page-logo', afroiabo_dna_tag_attribute( $tag, 'width' ), afroiabo_dna_tag_attribute( $tag, 'height' ) ); } } if ( preg_match_all( '/]*>/i', $html, $link_tags ) ) { foreach ( $link_tags[0] as $tag ) { $rel = strtolower( afroiabo_dna_tag_attribute( $tag, 'rel' ) ); if ( false === strpos( $rel, 'icon' ) ) { continue; } $size = afroiabo_dna_tag_attribute( $tag, 'sizes' ); preg_match( '/(\d{2,4})x(\d{2,4})/i', $size, $size_match ); $add_candidate( afroiabo_dna_tag_attribute( $tag, 'href' ), false !== strpos( $rel, 'apple-touch-icon' ) ? 56 : 38, 'site-icon', $size_match[1] ?? 0, $size_match[2] ?? 0 ); } } return $best; } function afroiabo_dna_image_pixel( $image, $x, $y ) { $color = imagecolorat( $image, $x, $y ); $channels = imagecolorsforindex( $image, $color ); return array( 'red' => (int) ( $channels['red'] ?? 0 ), 'green' => (int) ( $channels['green'] ?? 0 ), 'blue' => (int) ( $channels['blue'] ?? 0 ), 'alpha' => (int) ( $channels['alpha'] ?? 0 ), ); } function afroiabo_dna_rgb_distance( $left, $right ) { return sqrt( ( ( $left['red'] - $right['red'] ) ** 2 ) + ( ( $left['green'] - $right['green'] ) ** 2 ) + ( ( $left['blue'] - $right['blue'] ) ** 2 ) ); } function afroiabo_dna_remove_logo_canvas( $image ) { $width = imagesx( $image ); $height = imagesy( $image ); if ( $width < 8 || $height < 8 ) { return false; } $corners = array( afroiabo_dna_image_pixel( $image, 0, 0 ), afroiabo_dna_image_pixel( $image, $width - 1, 0 ), afroiabo_dna_image_pixel( $image, 0, $height - 1 ), afroiabo_dna_image_pixel( $image, $width - 1, $height - 1 ), ); $best_group = array(); foreach ( $corners as $reference ) { if ( $reference['alpha'] > 20 ) { continue; } $group = array_filter( $corners, static function ( $corner ) use ( $reference ) { return $corner['alpha'] <= 20 && afroiabo_dna_rgb_distance( $corner, $reference ) <= 34; } ); if ( count( $group ) > count( $best_group ) ) { $best_group = $group; } } if ( count( $best_group ) < 3 ) { return false; } $background = array( 'red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0 ); foreach ( $best_group as $corner ) { $background['red'] += $corner['red']; $background['green'] += $corner['green']; $background['blue'] += $corner['blue']; } $background['red'] = (int) round( $background['red'] / count( $best_group ) ); $background['green'] = (int) round( $background['green'] / count( $best_group ) ); $background['blue'] = (int) round( $background['blue'] / count( $best_group ) ); $edge_matches = 0; $edge_samples = 0; $sample_count = 24; for ( $index = 0; $index < $sample_count; $index++ ) { $x = (int) round( ( $width - 1 ) * $index / ( $sample_count - 1 ) ); $y = (int) round( ( $height - 1 ) * $index / ( $sample_count - 1 ) ); foreach ( array( array( $x, 0 ), array( $x, $height - 1 ), array( 0, $y ), array( $width - 1, $y ) ) as $point ) { $pixel = afroiabo_dna_image_pixel( $image, $point[0], $point[1] ); $edge_samples++; if ( $pixel['alpha'] <= 28 && afroiabo_dna_rgb_distance( $pixel, $background ) <= 45 ) { $edge_matches++; } } } if ( $edge_matches / max( 1, $edge_samples ) < 0.62 ) { return false; } $queue = array(); $visited = array(); $enqueue = static function ( $x, $y ) use ( &$queue, &$visited, $width, $height ) { if ( $x < 0 || $y < 0 || $x >= $width || $y >= $height ) { return; } $key = ( $y * $width ) + $x; if ( isset( $visited[ $key ] ) ) { return; } $visited[ $key ] = true; $queue[] = $key; }; for ( $x = 0; $x < $width; $x++ ) { $enqueue( $x, 0 ); $enqueue( $x, $height - 1 ); } for ( $y = 1; $y < $height - 1; $y++ ) { $enqueue( 0, $y ); $enqueue( $width - 1, $y ); } $matched = array(); for ( $head = 0; $head < count( $queue ); $head++ ) { $key = $queue[ $head ]; $x = $key % $width; $y = intdiv( $key, $width ); $pixel = afroiabo_dna_image_pixel( $image, $x, $y ); if ( $pixel['alpha'] > 32 || afroiabo_dna_rgb_distance( $pixel, $background ) > 52 ) { continue; } $matched[] = $key; $enqueue( $x - 1, $y ); $enqueue( $x + 1, $y ); $enqueue( $x, $y - 1 ); $enqueue( $x, $y + 1 ); } $total_pixels = $width * $height; if ( count( $matched ) < 4 || count( $matched ) > ( $total_pixels * 0.96 ) ) { return false; } imagealphablending( $image, false ); imagesavealpha( $image, true ); $transparent = imagecolorallocatealpha( $image, $background['red'], $background['green'], $background['blue'], 127 ); foreach ( $matched as $key ) { imagesetpixel( $image, $key % $width, intdiv( $key, $width ), $transparent ); } return true; } function afroiabo_dna_prepare_logo_png( $logo_url ) { $fallback = array( 'url' => esc_url_raw( $logo_url, array( 'http', 'https' ) ), 'width' => 0, 'height' => 0, ); if ( ! $fallback['url'] || ! wp_http_validate_url( $fallback['url'] ) || ! function_exists( 'imagecreatefromstring' ) ) { return $fallback; } $cache_key = md5( 'logo-cleanup-v2|' . $fallback['url'] ); $cache = get_option( 'afroiabo_dna_logo_png_cache', array() ); if ( is_array( $cache ) && ! empty( $cache[ $cache_key ]['url'] ) ) { $uploads = wp_upload_dir(); $cached_url = esc_url_raw( $cache[ $cache_key ]['url'], array( 'http', 'https' ) ); if ( 0 === strpos( $cached_url, $uploads['baseurl'] ) ) { $cached_file = $uploads['basedir'] . substr( $cached_url, strlen( $uploads['baseurl'] ) ); if ( is_file( $cached_file ) ) { return array( 'url' => $cached_url, 'width' => absint( $cache[ $cache_key ]['width'] ?? 0 ), 'height' => absint( $cache[ $cache_key ]['height'] ?? 0 ), ); } } } $response = wp_safe_remote_get( $fallback['url'], array( 'timeout' => 10, 'redirection' => 2, 'limit_response_size' => 2097152, 'user-agent' => 'AfroditaBot Brand Asset/' . AFROIABO_VERSION, ) ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return $fallback; } $contents = wp_remote_retrieve_body( $response ); if ( ! $contents || strlen( $contents ) > 2097152 ) { return $fallback; } $image_info = @getimagesizefromstring( $contents ); $source_width = absint( $image_info[0] ?? 0 ); $source_height = absint( $image_info[1] ?? 0 ); if ( ! $source_width || ! $source_height || ( $source_width * $source_height ) > 16000000 ) { return $fallback; } $source = @imagecreatefromstring( $contents ); if ( ! $source ) { return $fallback; } imagealphablending( $source, true ); imagesavealpha( $source, true ); if ( function_exists( 'imagecropauto' ) && defined( 'IMG_CROP_TRANSPARENT' ) ) { $cropped = @imagecropauto( $source, IMG_CROP_TRANSPARENT ); if ( $cropped ) { imagedestroy( $source ); $source = $cropped; $source_width = imagesx( $source ); $source_height = imagesy( $source ); } } $scale = min( 1, 360 / $source_width, 200 / $source_height ); $target_width = max( 1, (int) round( $source_width * $scale ) ); $target_height = max( 1, (int) round( $source_height * $scale ) ); $target = imagecreatetruecolor( $target_width, $target_height ); imagealphablending( $target, false ); imagesavealpha( $target, true ); $transparent = imagecolorallocatealpha( $target, 0, 0, 0, 127 ); imagefill( $target, 0, 0, $transparent ); imagecopyresampled( $target, $source, 0, 0, 0, 0, $target_width, $target_height, $source_width, $source_height ); imagedestroy( $source ); afroiabo_dna_remove_logo_canvas( $target ); if ( function_exists( 'imagecropauto' ) && defined( 'IMG_CROP_TRANSPARENT' ) ) { $clean_crop = @imagecropauto( $target, IMG_CROP_TRANSPARENT ); if ( $clean_crop ) { imagedestroy( $target ); $target = $clean_crop; $target_width = imagesx( $target ); $target_height = imagesy( $target ); imagealphablending( $target, false ); imagesavealpha( $target, true ); } } ob_start(); imagepng( $target, null, 9 ); $png_data = ob_get_clean(); imagedestroy( $target ); if ( ! $png_data ) { return $fallback; } $upload = wp_upload_bits( 'afroditabot-logo-' . $cache_key . '.png', null, $png_data ); if ( ! empty( $upload['error'] ) || empty( $upload['url'] ) ) { return $fallback; } if ( ! empty( $upload['file'] ) && is_file( $upload['file'] ) ) { @chmod( $upload['file'], 0644 ); } $prepared = array( 'url' => esc_url_raw( $upload['url'], array( 'http', 'https' ) ), 'width' => $target_width, 'height' => $target_height, ); if ( ! is_array( $cache ) ) { $cache = array(); } $cache[ $cache_key ] = $prepared; if ( count( $cache ) > 12 ) { $cache = array_slice( $cache, -12, null, true ); } update_option( 'afroiabo_dna_logo_png_cache', $cache, false ); return $prepared; } function afroiabo_dna_same_site_stylesheets( $html, $site_url ) { $styles = array(); $site_host = wp_parse_url( $site_url, PHP_URL_HOST ); if ( ! $site_host || ! is_string( $html ) ) { return $styles; } preg_match_all( '/]*\brel=["\'][^"\']*stylesheet[^"\']*["\'][^>]*\bhref=["\']([^"\']+)["\'][^>]*>|]*\bhref=["\']([^"\']+)["\'][^>]*\brel=["\'][^"\']*stylesheet[^"\']*["\'][^>]*>/i', $html, $matches ); $urls = array_filter( array_merge( $matches[1] ?? array(), $matches[2] ?? array() ) ); foreach ( array_slice( array_unique( $urls ), 0, 6 ) as $stylesheet_url ) { $stylesheet_url = afroiabo_dna_resolve_url( $stylesheet_url, $site_url ); if ( ! $stylesheet_url || wp_parse_url( $stylesheet_url, PHP_URL_HOST ) !== $site_host ) { continue; } $response = wp_safe_remote_get( $stylesheet_url, array( 'timeout' => 5, 'redirection' => 1, 'limit_response_size' => 180000 ) ); if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) { $styles[] = wp_remote_retrieve_body( $response ); } } return $styles; } function afroiabo_dna_key_pages( $html, $site_url, $limit = 10 ) { $documents = array(); $site_host = strtolower( (string) wp_parse_url( $site_url, PHP_URL_HOST ) ); if ( ! $site_host || ! is_string( $html ) ) { return $documents; } preg_match_all( '/]*\bhref\s*=\s*(["\'])(.*?)\1[^>]*>(.*?)<\/a>/is', $html, $links, PREG_SET_ORDER ); $ranked = array(); $intent_words = array( 'about', 'acerca', 'nosotros', 'empresa', 'equipo', 'historia', 'mission', 'mision', 'service', 'servicio', 'product', 'producto', 'shop', 'tienda', 'catalog', 'catalogo', 'contact', 'contacto', 'location', 'ubicacion', 'sede', 'faq', 'preguntas', 'help', 'ayuda', 'price', 'precio', 'plan', 'tarifa', 'booking', 'reserva', 'appointment', 'cita', 'schedule', 'opening', 'hours', 'horario', 'entrada', 'acceso', 'instalacion', 'instalación', 'course', 'curso', 'program', 'programa', 'portfolio', 'proyecto', 'case-stud', 'caso', 'event', 'evento', 'blog', 'news', 'noticia', 'resource', 'recurso', 'support', 'soporte', 'policy', 'politica', 'terms', 'condiciones', 'shipping', 'envio', 'return', 'devolucion', 'donate', 'donar', 'membership', 'miembro', 'subscribe', 'suscri', 'menu', 'carta', ); foreach ( array_slice( $links, 0, 240 ) as $link ) { $resolved = afroiabo_dna_resolve_url( $link[2] ?? '', $site_url ); if ( ! $resolved || strtolower( (string) wp_parse_url( $resolved, PHP_URL_HOST ) ) !== $site_host ) { continue; } $parts = wp_parse_url( $resolved ); $path = $parts['path'] ?? '/'; if ( '/' === $path || preg_match( '#/(?:wp-admin|wp-login|login|logout|account|checkout|cart)(?:/|$)#i', $path ) ) { continue; } if ( preg_match( '/\.(?:jpe?g|png|gif|webp|svg|pdf|zip|xml|json|css|js)$/i', $path ) ) { continue; } $normalized_url = ( $parts['scheme'] ?? 'https' ) . '://' . $parts['host'] . ( isset( $parts['port'] ) ? ':' . (int) $parts['port'] : '' ) . $path; $signal = strtolower( rawurldecode( $path ) . ' ' . wp_strip_all_tags( $link[3] ?? '' ) ); $score = max( 0, 5 - substr_count( trim( $path, '/' ), '/' ) ); if ( preg_match( '/precio|tarifa|cuota|coste|horario|opening|hours|schedule/i', $signal ) ) { $score += 55; } if ( preg_match( '/contacto|contact|ubicaci[oó]n|location|direcci[oó]n|reserva|booking|appointment|cita/i', $signal ) ) { $score += 30; } if ( preg_match( '#/(?:evento|event|noticia|news|author)/[^/]+/?$#i', $path ) ) { $score -= 30; } foreach ( $intent_words as $word ) { if ( false !== strpos( $signal, $word ) ) { $score += 9; } } $ranked[ $normalized_url ] = max( $ranked[ $normalized_url ] ?? 0, $score ); } arsort( $ranked ); $remaining_bytes = 20 * MB_IN_BYTES; foreach ( array_slice( array_keys( $ranked ), 0, max( 0, (int) $limit ) ) as $page_url ) { if ( $remaining_bytes < 120000 ) { break; } $response = wp_safe_remote_get( $page_url, array( 'timeout' => 5, 'redirection' => 1, // Algunas plantillas insertan más de 1 MB de CSS antes del contenido. // Un límite pequeño cortaría el documento dentro de