%s', esc_url( self::page_url() ), esc_html__( 'Ajustes', 'ai-bots-doxi-chat' ) ); array_unshift( $links, $settings ); return $links; } /* --------------------------------------------------------------------------- * Acciones * ------------------------------------------------------------------------ */ public static function handle_request_code() { self::guard(); check_admin_referer( 'doxi_request_code' ); $email = isset( $_POST['doxi_email'] ) ? sanitize_email( wp_unslash( $_POST['doxi_email'] ) ) : ''; if ( ! is_email( $email ) ) { self::notice( 'error', __( 'Escribe un correo válido.', 'ai-bots-doxi-chat' ) ); self::back(); } $domains = Doxi_Settings::site_domains(); if ( empty( $domains ) ) { self::notice( 'error', __( 'No pudimos leer la dirección de esta web. Revisa la «Dirección del sitio» en Ajustes → Generales.', 'ai-bots-doxi-chat' ) ); self::back(); } $result = Doxi_Api::request_code( $email, $domains ); if ( ! $result['ok'] ) { self::notice( 'error', self::message_for( $result['error'] ) ); self::back(); } Doxi_Settings::update( array( 'pending_email' => $email, 'pending_at' => time(), ) ); // El backend responde lo mismo exista o no la cuenta (para que esta pantalla no // sirva para averiguar quién es cliente), así que el mensaje tampoco lo afirma. self::notice( 'success', __( 'Si ese correo es el de tu cuenta Doxi, te acabamos de mandar un código. Vale 15 minutos. Si no lo ves, revisa la carpeta de spam.', 'ai-bots-doxi-chat' ) ); self::back(); } public static function handle_activate() { self::guard(); check_admin_referer( 'doxi_activate' ); $email = (string) Doxi_Settings::get( 'pending_email' ); if ( '' === $email ) { self::notice( 'error', __( 'Primero pide un código con el correo de tu cuenta Doxi.', 'ai-bots-doxi-chat' ) ); self::back(); } $code = isset( $_POST['doxi_code'] ) ? self::clean_code( sanitize_text_field( wp_unslash( $_POST['doxi_code'] ) ) ) : ''; if ( '' === $code ) { self::notice( 'error', __( 'Pega el código que te llegó por correo.', 'ai-bots-doxi-chat' ) ); self::back(); } $domains = Doxi_Settings::site_domains(); $result = Doxi_Api::activate( $email, $code, $domains ); if ( ! $result['ok'] ) { self::notice( 'error', self::message_for( $result['error'] ) ); self::back(); } $data = $result['data']; $api_key = isset( $data['apiKey'] ) && is_string( $data['apiKey'] ) ? trim( $data['apiKey'] ) : ''; // Un 200 sin una clave con forma de clave no es un éxito: guardarla dejaría un // chat que no monta, y la pantalla diría «conectado». if ( ! Doxi_Settings::is_valid_api_key( $api_key ) ) { self::notice( 'error', self::message_for( 'respuesta_invalida' ) ); self::back(); } $bot_name = isset( $data['nombreDelBot'] ) && is_string( $data['nombreDelBot'] ) ? sanitize_text_field( $data['nombreDelBot'] ) : ''; // Se pinta lo que DEVOLVIÓ el backend, no lo que se mandó: si normalizó algo, // verlo es la única forma de entender por qué. $authorized = $domains; if ( isset( $data['dominios'] ) && is_array( $data['dominios'] ) ) { $authorized = array_values( array_filter( array_map( 'sanitize_text_field', $data['dominios'] ) ) ); } Doxi_Settings::update( array( 'api_key' => $api_key, 'bot_name' => $bot_name, 'domains' => $authorized, 'connected_at' => time(), 'pending_email' => '', 'pending_at' => 0, ) ); Doxi_Frontend::purge_page_cache(); self::notice( 'success', '' !== $bot_name /* translators: %s: nombre del bot. */ ? sprintf( __( '¡Listo! Tu Doxi «%s» ya está conectado a esta web.', 'ai-bots-doxi-chat' ), $bot_name ) : __( '¡Listo! Tu Doxi ya está conectado a esta web.', 'ai-bots-doxi-chat' ) ); self::back(); } public static function handle_change_email() { self::guard(); check_admin_referer( 'doxi_change_email' ); Doxi_Settings::update( array( 'pending_email' => '', 'pending_at' => 0, ) ); self::back(); } public static function handle_disconnect() { self::guard(); check_admin_referer( 'doxi_disconnect' ); Doxi_Settings::disconnect(); Doxi_Frontend::purge_page_cache(); self::notice( 'success', __( 'Se desconectó. El chat ya no aparece en tu web. Los dominios siguen autorizados en tu cuenta Doxi: si vuelves a conectar, pide un código nuevo.', 'ai-bots-doxi-chat' ) ); self::back(); } public static function handle_save_options() { self::guard(); check_admin_referer( 'doxi_save_options' ); $changes = array(); // Las páginas solo se tocan si el formulario las enseñó; leer su ausencia como // «todas sin chat» las apagaría todas. if ( isset( $_POST['doxi_pages_shown'] ) ) { // Se marca dónde SÍ aparece el chat (es lo que la gente espera al ver una // lista de páginas con casillas), pero se guarda lo contrario: la lista de // excluidas. Así una página nueva nace CON chat sin tener que volver aquí. $con_chat = isset( $_POST['doxi_pages_with_chat'] ) && is_array( $_POST['doxi_pages_with_chat'] ) ? array_map( 'absint', wp_unslash( $_POST['doxi_pages_with_chat'] ) ) : array(); // Solo se decide sobre las páginas que el formulario enseñó. Si alguien creó // una página mientras esta pantalla estaba abierta, no se queda sin chat por // no haber estado en la lista. $listadas = isset( $_POST['doxi_pages_listed'] ) ? array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $_POST['doxi_pages_listed'] ) ) ) ) : array(); $changes['excluded_pages'] = array_values( array_diff( array_filter( $listadas ), $con_chat ) ); } Doxi_Settings::update( $changes ); Doxi_Frontend::purge_page_cache(); self::notice( 'success', __( 'Cambios guardados.', 'ai-bots-doxi-chat' ) ); self::back(); } /* --------------------------------------------------------------------------- * Pantalla * ------------------------------------------------------------------------ */ public static function render() { if ( ! current_user_can( 'manage_options' ) ) { return; } $settings = Doxi_Settings::all(); ?>

' . esc_html( $host ) . '' ); ?>


$settings */ private static function render_code_step( array $settings ) { ?>

' . esc_html( (string) $settings['pending_email'] ) . '' ); ?>


$settings */ private static function render_connected( array $settings ) { $bot_name = (string) $settings['bot_name']; $domains = (array) $settings['domains']; $excluded = array_map( 'absint', (array) $settings['excluded_pages'] ); $pages = get_pages( array( 'sort_column' => 'post_title', 'post_status' => 'publish', 'number' => 200, ) ); ?>

«' . esc_html( $bot_name ) . '»' ); } else { esc_html_e( 'Tu Doxi está conectado a esta web.', 'ai-bots-doxi-chat' ); } ?>

403 ) ); } } /** * Un espacio o un salto de línea pegado al copiar del correo es el error más * probable de todo el flujo, y no cuesta nada quitarlo. Mayúsculas, porque el * cliente puede teclearlo en minúsculas. * * @param string $raw */ private static function clean_code( $raw ) { $code = strtoupper( sanitize_text_field( (string) $raw ) ); return (string) preg_replace( '/[^A-Z0-9-]/', '', $code ); } /** * Cada motivo que puede mandar el backend, dicho para el dueño de la web. * * @param string $reason */ private static function message_for( $reason ) { $messages = array( 'codigo_invalido' => __( 'Ese código no es correcto. Revísalo o pide uno nuevo.', 'ai-bots-doxi-chat' ), 'codigo_vencido' => __( 'Ese código ya venció: duran 15 minutos. Pide uno nuevo.', 'ai-bots-doxi-chat' ), 'codigo_usado' => __( 'Ese código ya se usó. Cada código sirve una sola vez: pide uno nuevo.', 'ai-bots-doxi-chat' ), 'codigo_anulado' => __( 'Ese código se anuló por demasiados intentos. Pide uno nuevo.', 'ai-bots-doxi-chat' ), 'codigo_reemplazado' => __( 'Ese código ya no vale: pediste uno más nuevo. Usa el último que te llegó.', 'ai-bots-doxi-chat' ), 'correo_no_disponible' => __( 'Ahora mismo Doxi no puede enviar correos. Inténtalo más tarde.', 'ai-bots-doxi-chat' ), 'demasiados_dominios' => __( 'Tu Doxi ya está autorizado en el máximo de webs. Quita alguna desde tu panel de Doxi.', 'ai-bots-doxi-chat' ), 'datos_invalidos' => __( 'Faltan datos. Revisa el correo y la «Dirección del sitio» en Ajustes → Generales.', 'ai-bots-doxi-chat' ), 'dominio_no_coincide' => __( 'Ese código se pidió desde otra web. Pide uno nuevo desde aquí.', 'ai-bots-doxi-chat' ), 'bot_inactivo' => __( 'Tu Doxi no está activo. Revísalo en tu panel de Doxi.', 'ai-bots-doxi-chat' ), 'demasiados_intentos' => __( 'Hubo demasiados intentos seguidos. Espera unos minutos y vuelve a probar.', 'ai-bots-doxi-chat' ), 'sin_conexion' => __( 'No pudimos conectar con Doxi. Revisa la conexión de tu servidor e inténtalo de nuevo.', 'ai-bots-doxi-chat' ), 'respuesta_invalida' => __( 'Doxi respondió algo inesperado. Inténtalo de nuevo en unos minutos.', 'ai-bots-doxi-chat' ), ); if ( isset( $messages[ $reason ] ) ) { return $messages[ $reason ]; } return __( 'No se pudo completar. Inténtalo de nuevo en unos minutos.', 'ai-bots-doxi-chat' ); } /** * El aviso sobrevive a la redirección en un transient por usuario, y se borra al * enseñarse: un aviso que reaparece al recargar se lee como que pasó dos veces. * * @param string $type success | error | warning | info * @param string $message */ private static function notice( $type, $message ) { set_transient( 'doxi_notice_' . get_current_user_id(), array( 'type' => $type, 'message' => $message, ), MINUTE_IN_SECONDS ); } private static function print_notice() { $key = 'doxi_notice_' . get_current_user_id(); $notice = get_transient( $key ); if ( ! is_array( $notice ) || empty( $notice['message'] ) ) { return; } delete_transient( $key ); $type = in_array( $notice['type'], array( 'success', 'error', 'warning', 'info' ), true ) ? $notice['type'] : 'info'; printf( '

%2$s

', esc_attr( $type ), esc_html( (string) $notice['message'] ) ); } private static function page_url() { return admin_url( 'options-general.php?page=' . self::PAGE ); } /** * @return never */ private static function back() { wp_safe_redirect( self::page_url() ); exit; } }