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 '| Fecha | Acción | Contacto | Mensaje y conversación |
';
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 '| ' . esc_html( get_the_date( 'd/m/Y H:i', $lead ) ) . ' | ';
echo '' . esc_html( afroiabo_lead_type_label( $type ) ) . ' | ';
echo '' . esc_html( $name ) . ' ' . esc_html( $email ) . ' ' . esc_html( $phone ) . ' | ';
echo '' . esc_html( $lead->post_content ) . '';
if ( $transcript ) {
echo 'Ver conversación adjunta' . esc_html( $transcript ) . ' ';
}
echo ' |
';
}
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( '/