'array', 'sanitize_callback' => array( __CLASS__, 'sanear_ajustes' ), 'default' => array(), ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- solo se lee qué página es; el regreso se valida con `state`. if ( isset( $_GET['page'] ) && 'validafirma-ajustes' === sanitize_key( wp_unslash( $_GET['page'] ) ) ) { $codigo = Validafirma_Conexion::procesar_regreso(); if ( null !== $codigo ) { self::avisar_codigo( $codigo ); wp_safe_redirect( admin_url( 'admin.php?page=validafirma-ajustes' ) ); exit; } } } /** * Limpia los ajustes que llegan del formulario. * * @param mixed $entrada Datos enviados. * @return array */ public static function sanear_ajustes( $entrada ) { $entrada = is_array( $entrada ) ? $entrada : array(); $correo = isset( $entrada['correo_avisos'] ) ? sanitize_email( (string) $entrada['correo_avisos'] ) : ''; $tope = isset( $entrada['tope_diario'] ) ? (int) $entrada['tope_diario'] : 40; return array( 'correo_avisos' => is_email( $correo ) ? $correo : '', 'tope_diario' => min( 500, max( 1, $tope ) ), 'borrar_al_desinstalar' => ! empty( $entrada['borrar_al_desinstalar'] ), ); } /** * Menú «ValidaFirma». * * @return void */ public static function menu() { $icono = self::icono_de_menu(); add_menu_page( __( 'ValidaFirma', 'validafirma' ), __( 'ValidaFirma', 'validafirma' ), Validafirma_Permisos::CAPACIDAD, 'validafirma', array( __CLASS__, 'pagina_panel' ), $icono, 58 ); add_submenu_page( 'validafirma', __( 'Panel de ValidaFirma', 'validafirma' ), __( 'Panel', 'validafirma' ), Validafirma_Permisos::CAPACIDAD, 'validafirma', array( __CLASS__, 'pagina_panel' ) ); add_submenu_page( 'validafirma', __( 'Enviar a firma', 'validafirma' ), __( 'Enviar a firma', 'validafirma' ), Validafirma_Permisos::CAPACIDAD, 'validafirma-enviar', array( __CLASS__, 'pagina_enviar' ) ); add_submenu_page( 'validafirma', __( 'Ajustes de ValidaFirma', 'validafirma' ), __( 'Ajustes', 'validafirma' ), 'manage_options', 'validafirma-ajustes', array( __CLASS__, 'pagina_ajustes' ) ); add_submenu_page( 'validafirma', __( 'Pide tu integración', 'validafirma' ), __( 'Pide tu integración', 'validafirma' ), 'manage_options', 'validafirma-integraciones', array( __CLASS__, 'pagina_integraciones' ) ); add_submenu_page( 'validafirma', __( 'Ayuda de ValidaFirma', 'validafirma' ), __( 'Ayuda', 'validafirma' ), Validafirma_Permisos::CAPACIDAD, 'validafirma-ayuda', array( __CLASS__, 'pagina_ayuda' ) ); } /** * Isotipo de ValidaFirma como ícono del menú, en data URI. * * WordPress lo repinta con el color del esquema del administrador (svg-painter), así que va a una tinta, con el * candado calado en el disco (`assets/img/isotipo-menu.svg`, copia de `activos/favicon/safari-pinned-tab.svg` de la marca). Si el archivo no se puede leer, el menú usa el * ícono genérico de WordPress. * * @return string */ private static function icono_de_menu() { $svg = (string) file_get_contents( VALIDAFIRMA_DIR . 'assets/img/isotipo-menu.svg' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- archivo propio del plugin. if ( '' === $svg ) { return 'dashicons-privacy'; } return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- los íconos de menú de WordPress se entregan así. } /** * Cabecera de marca: la horizontal, discreta, arriba del título de WordPress. Es el único lugar que la dibuja; * `abrir_pantalla()` la pone en cada pantalla del plugin. Va el logo a color porque el fondo del administrador es * claro en todos los esquemas de color. * * @return void */ private static function cabecera_de_marca() { echo '
' . esc_attr__( 'ValidaFirma', 'validafirma' ) . '
'; } /** * Abre el contenedor de una pantalla del plugin, con su cabecera de marca. Lo cierra quien lo abre con ``. * * @param bool $con_marca Falso en la pantalla de conectar, donde la marca va con más presencia en su propia tarjeta. * @return void */ private static function abrir_pantalla( $con_marca = true ) { echo '
'; if ( $con_marca ) { self::cabecera_de_marca(); } } /** * Estilos y scripts, solo en las pantallas del plugin. * * @param string $gancho Sufijo de la pantalla. * @return void */ public static function recursos( $gancho ) { if ( false === strpos( (string) $gancho, 'validafirma' ) ) { return; } wp_enqueue_style( 'validafirma-admin', VALIDAFIRMA_URL . 'assets/css/admin.css', array(), VALIDAFIRMA_VERSION ); if ( false !== strpos( $gancho, 'validafirma-enviar' ) ) { wp_enqueue_script( 'validafirma-admin', VALIDAFIRMA_URL . 'assets/js/admin.js', array(), VALIDAFIRMA_VERSION, true ); } if ( false !== strpos( $gancho, 'validafirma-integraciones' ) ) { wp_enqueue_script( 'validafirma-pide', VALIDAFIRMA_URL . 'assets/js/pide-integracion.js', array(), VALIDAFIRMA_VERSION, true ); wp_localize_script( 'validafirma-pide', 'validafirmaPide', array( 'comunicacionUrl' => untrailingslashit( VALIDAFIRMA_COMUNICACION_URL ), 'formularioId' => (string) VALIDAFIRMA_FORMULARIO_INTEGRACION, 'textos' => array( 'enviando' => __( 'Comprobando que eres una persona…', 'validafirma' ), 'listo' => __( 'Tu solicitud quedó registrada.', 'validafirma' ), 'numero' => __( 'Número de solicitud:', 'validafirma' ), 'sinMarcas' => __( 'Marca al menos un plugin o escribe cuál usas.', 'validafirma' ), 'sinCorreo' => __( 'Escribe un correo válido.', 'validafirma' ), 'e401' => __( 'La sesión del formulario venció. Vuelve a apretar «Enviar».', 'validafirma' ), 'e403' => __( 'No se pudo enviar la solicitud desde este sitio. Usa la página del formulario.', 'validafirma' ), 'e422' => __( 'Revisa los datos: alguno no es válido.', 'validafirma' ), 'e429' => __( 'Hay demasiados intentos. Espera un momento y vuelve a intentarlo.', 'validafirma' ), 'eRed' => __( 'No se pudo comunicar con ValidaFirma. Vuelve a intentarlo más tarde.', 'validafirma' ), ), ) ); } } /** * Guarda un aviso para mostrarlo en la próxima pantalla. * * @param string $tipo success | error | warning | info. * @param string $mensaje Texto ya traducido y sin datos de personas. * @param bool $recargar Si el aviso lleva el botón «Recargar». * @return void */ public static function avisar( $tipo, $mensaje, $recargar = false ) { set_transient( 'validafirma_aviso_' . get_current_user_id(), array( 'tipo' => $tipo, 'mensaje' => $mensaje, 'recargar' => $recargar, ), MINUTE_IN_SECONDS ); } /** * Aviso a partir del código que devuelve la conexión. * * @param string $codigo Código. * @return void */ private static function avisar_codigo( $codigo ) { $mensajes = array( 'conectado' => array( 'success', __( 'Listo: tu cuenta quedó conectada. El sitio está en modo de prueba: los documentos son de ejemplo, no se cobran y no sale ningún mensaje.', 'validafirma' ) ), 'produccion' => array( 'success', __( 'El sitio pasó a producción. Desde ahora los envíos son de verdad y se cobran con la tarifa de siempre.', 'validafirma' ) ), 'cancelado' => array( 'warning', __( 'No se hizo ningún cambio: cancelaste en ValidaFirma.', 'validafirma' ) ), 'intento_vencido' => array( 'error', __( 'El intento de conexión venció o no es válido. Vuelve a intentarlo.', 'validafirma' ) ), 'error_conexion' => array( 'error', __( 'No se pudo completar la conexión con ValidaFirma. Vuelve a intentarlo.', 'validafirma' ) ), 'tope_de_llaves' => array( 'error', __( 'Tu cuenta llegó al tope de llaves activas. Revoca alguna en ValidaFirma y vuelve a conectar.', 'validafirma' ) ), 'sin_permiso' => array( 'error', __( 'Solo los administradores del sitio pueden conectar la cuenta.', 'validafirma' ) ), ); $par = isset( $mensajes[ $codigo ] ) ? $mensajes[ $codigo ] : array( 'error', __( 'No se pudo completar la operación.', 'validafirma' ) ); self::avisar( $par[0], $par[1] ); } /** * Muestra (y consume) el aviso pendiente. * * @return void */ private static function mostrar_aviso() { $clave = 'validafirma_aviso_' . get_current_user_id(); $aviso = get_transient( $clave ); if ( ! is_array( $aviso ) ) { return; } delete_transient( $clave ); $clase = in_array( $aviso['tipo'], array( 'success', 'error', 'warning', 'info' ), true ) ? $aviso['tipo'] : 'info'; echo '

' . esc_html( $aviso['mensaje'] ); if ( ! empty( $aviso['recargar'] ) ) { echo ' ' . esc_html__( 'Recargar', 'validafirma' ) . ''; } echo '

'; } /** * Redirige a una pantalla del plugin y termina. * * @param array $args Parámetros de la URL. * @return void */ private static function volver( array $args ) { wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) ); exit; } /** * Carga una plantilla de `vistas/`. * * @param string $nombre Nombre del archivo sin extensión. * @param array $datos Datos para la plantilla. * @return void */ private static function vista( $nombre, array $datos = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter -- la plantilla incluida lee $datos. include VALIDAFIRMA_DIR . 'includes/admin/vistas/' . $nombre . '.php'; } /** * Insignia con el modo del sitio. * * @param string $modo prueba | produccion. * @return string HTML ya escapado. */ public static function insignia_de_modo( $modo ) { if ( 'produccion' === $modo ) { return '' . esc_html__( 'Producción', 'validafirma' ) . ''; } return '' . esc_html__( 'Modo de prueba', 'validafirma' ) . ''; } /** * Saldo de la cuenta, con caché corta. Null en cuentas corporativas. * * @return array|WP_Error|null */ private static function saldo() { if ( Validafirma_Conexion::es_corporativa() ) { return null; } $clave = 'validafirma_saldo_' . Validafirma_Conexion::modo(); $guardado = get_transient( $clave ); if ( is_array( $guardado ) ) { return $guardado; } $respuesta = Validafirma_Cliente_API::instancia()->saldo(); if ( is_wp_error( $respuesta ) ) { return $respuesta; } $cuerpo = is_array( $respuesta['cuerpo'] ) ? $respuesta['cuerpo'] : array(); set_transient( $clave, $cuerpo, MINUTE_IN_SECONDS ); return $cuerpo; } /* * --------------------------------------------------------------------------------------------- * Pantallas * --------------------------------------------------------------------------------------------- */ /** * Pantalla principal: conexión o panel. * * @return void */ public static function pagina_panel() { Validafirma_Permisos::exigir_uso(); $estado = Validafirma_Conexion::estado(); $conectada = $estado && 'conectada' === $estado['estado']; $hay_envios = array_sum( Validafirma_Envios::contar_vistas() ) > 0; // phpcs:disable WordPress.Security.NonceVerification.Recommended -- navegación de solo lectura. $accion = isset( $_GET['accion'] ) ? sanitize_key( wp_unslash( $_GET['accion'] ) ) : ''; $envio_id = isset( $_GET['envio'] ) ? absint( $_GET['envio'] ) : 0; $que = isset( $_GET['que'] ) ? sanitize_key( wp_unslash( $_GET['que'] ) ) : ''; $vista = isset( $_GET['vista'] ) ? sanitize_key( wp_unslash( $_GET['vista'] ) ) : ''; // phpcs:enable // Sin conexión se ve la pantalla de conectar y, debajo, la lista que quedó en el sitio (RN-20, CA-35). $es_conectar = ! $conectada && ! ( $hay_envios && $envio_id && in_array( $accion, array( 'detalle', 'confirmar' ), true ) ); self::abrir_pantalla( ! $es_conectar ); self::mostrar_aviso(); if ( $es_conectar ) { self::vista( 'conexion', array( 'estado' => $estado ) ); if ( $hay_envios ) { if ( ! in_array( $vista, array( 'produccion', 'prueba', 'sin_enviar' ), true ) ) { $conteos = Validafirma_Envios::contar_vistas(); $vista = 'prueba'; foreach ( array( 'prueba', 'produccion', 'sin_enviar' ) as $candidata ) { if ( ! empty( $conteos[ $candidata ] ) ) { $vista = $candidata; break; } } } $tabla = new Validafirma_Tabla_Envios( $vista ); $tabla->prepare_items(); echo '

' . esc_html__( 'Envíos guardados en el sitio', 'validafirma' ) . '

'; $tabla->views(); $tabla->display(); } echo '
'; return; } if ( $envio_id && in_array( $accion, array( 'detalle', 'confirmar' ), true ) ) { $envio = Validafirma_Envios::obtener( $envio_id ); if ( ! $envio ) { echo '

' . esc_html__( 'ValidaFirma', 'validafirma' ) . '

' . esc_html__( 'Ese envío ya no existe en el sitio.', 'validafirma' ) . '

'; return; } if ( 'detalle' === $accion ) { self::pagina_detalle( $envio ); } else { self::vista( 'confirmar', array( 'envio' => $envio, 'que' => 'borrar' === $que ? 'borrar' : 'cancelar', ) ); } echo ''; return; } if ( ! in_array( $vista, array( 'produccion', 'prueba', 'sin_enviar' ), true ) ) { $vista = Validafirma_Conexion::modo(); } $tabla = new Validafirma_Tabla_Envios( $vista ); $tabla->prepare_items(); self::vista( 'panel', array( 'estado' => $estado, 'saldo' => self::saldo(), 'tabla' => $tabla, ) ); echo ''; } /** * Detalle de un envío; en los de prueba, con las simulaciones y los mensajes simulados. * * @param array $envio Envío. * @return void */ private static function pagina_detalle( array $envio ) { $firmantes_api = array(); $mensajes = array(); $error_api = ''; if ( Validafirma_Conexion::conectada() && $envio['es_prueba'] && ! empty( $envio['documento_id'] ) ) { if ( 'pendiente' === $envio['estado'] ) { // Una consulta a lo más por minuto y por envío; si el documento terminó, se aplica el cambio. $clave = 'validafirma_det_' . $envio['id']; $vivo = get_transient( $clave ); if ( ! is_array( $vivo ) ) { $cuerpo = Validafirma_Revision::revisar_envio( $envio ); if ( is_wp_error( $cuerpo ) ) { $error_api = $cuerpo->get_error_message(); } else { $vivo = $cuerpo; set_transient( $clave, $vivo, MINUTE_IN_SECONDS ); } } if ( is_array( $vivo ) && isset( $vivo['firmantes'] ) && is_array( $vivo['firmantes'] ) ) { $firmantes_api = $vivo['firmantes']; } $envio = Validafirma_Envios::obtener( $envio['id'] ); } $respuesta = Validafirma_Cliente_API::instancia()->mensajes_simulados( $envio['documento_id'] ); if ( ! is_wp_error( $respuesta ) && isset( $respuesta['cuerpo']['mensajes'] ) && is_array( $respuesta['cuerpo']['mensajes'] ) ) { $mensajes = $respuesta['cuerpo']['mensajes']; } } self::vista( 'detalle', array( 'envio' => $envio, 'firmantes_api' => $firmantes_api, 'mensajes' => $mensajes, 'error_api' => $error_api, ) ); } /** * Pantalla «Enviar a firma». * * @return void */ public static function pagina_enviar() { Validafirma_Permisos::exigir_uso(); self::abrir_pantalla(); self::mostrar_aviso(); if ( ! Validafirma_Conexion::conectada() ) { echo '

' . esc_html__( 'Enviar a firma', 'validafirma' ) . '

' . esc_html__( 'Primero conecta tu cuenta de ValidaFirma.', 'validafirma' ) . ' ' . esc_html__( 'Ir al panel', 'validafirma' ) . '

'; return; } $clave = 'validafirma_form_' . get_current_user_id(); $previo = get_transient( $clave ); delete_transient( $clave ); self::vista( 'enviar', array( 'modo' => Validafirma_Conexion::modo(), 'previo' => is_array( $previo ) ? $previo : array(), ) ); echo ''; } /** * Pantalla de ajustes (solo administradores). * * @return void */ public static function pagina_ajustes() { Validafirma_Permisos::exigir_administradora(); self::abrir_pantalla(); self::mostrar_aviso(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- navegación de solo lectura. $paso = isset( $_GET['paso'] ) ? sanitize_key( wp_unslash( $_GET['paso'] ) ) : ''; self::vista( 'ajustes', array( 'estado' => Validafirma_Conexion::estado(), 'paso' => $paso, 'sin_desatendido' => Validafirma_Integraciones::formularios_sin_desatendido_de_produccion(), ) ); echo ''; } /** * Los plugins del sitio, cuáles tienen integración y «Pide tu integración» (solo administradores). Tiene su propia * entrada en el menú: al fondo de Ajustes no se encontraba, y otros plugins llaman «Integración» a su propio menú. * * @return void */ public static function pagina_integraciones() { Validafirma_Permisos::exigir_administradora(); self::abrir_pantalla(); self::mostrar_aviso(); self::vista( 'integraciones' ); echo ''; } /** * Pantalla de ayuda. * * @return void */ public static function pagina_ayuda() { Validafirma_Permisos::exigir_uso(); self::abrir_pantalla(); self::vista( 'ayuda' ); echo ''; } /* * --------------------------------------------------------------------------------------------- * Acciones (admin-post.php). Cada una: capacidad, nonce, trabajo, redirección. * --------------------------------------------------------------------------------------------- */ /** * Lee el id de envío de la petición y lo valida contra el nonce de la acción. * * @param string $accion Nombre de la acción (para el nonce). * @return array Envío. */ private static function envio_de_la_peticion( $accion ) { // phpcs:ignore WordPress.Security.NonceVerification -- se verifica justo abajo, con el id incluido en la acción del nonce. $id = isset( $_REQUEST['envio'] ) ? absint( $_REQUEST['envio'] ) : 0; check_admin_referer( 'validafirma_' . $accion . '_' . $id ); $envio = $id ? Validafirma_Envios::obtener( $id ) : null; if ( ! $envio ) { wp_die( esc_html__( 'Ese envío ya no existe.', 'validafirma' ), '', array( 'response' => 404 ) ); } return $envio; } /** * Inicia la conexión: lleva a ValidaFirma. * * @return void */ public static function accion_conectar() { Validafirma_Permisos::exigir_administradora(); check_admin_referer( 'validafirma_conectar' ); $url = Validafirma_Conexion::iniciar( 'conectar' ); if ( is_wp_error( $url ) ) { self::avisar( 'error', $url->get_error_message() ); self::volver( array( 'page' => 'validafirma' ) ); } wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- el destino es ValidaFirma, definido por constante. exit; } /** * Inicia el paso a producción: lleva a ValidaFirma a que la persona lo apruebe. * * @return void */ public static function accion_produccion() { Validafirma_Permisos::exigir_administradora(); check_admin_referer( 'validafirma_produccion' ); $url = Validafirma_Conexion::iniciar( 'produccion' ); if ( is_wp_error( $url ) ) { self::avisar( 'error', $url->get_error_message() ); self::volver( array( 'page' => 'validafirma-ajustes' ) ); } wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- el destino es ValidaFirma, definido por constante. exit; } /** * Vuelve a prueba o a producción sin ir a ValidaFirma. * * @return void */ public static function accion_modo() { Validafirma_Permisos::exigir_administradora(); check_admin_referer( 'validafirma_modo' ); $modo = isset( $_POST['modo'] ) && 'produccion' === sanitize_key( wp_unslash( $_POST['modo'] ) ) ? 'produccion' : 'prueba'; if ( Validafirma_Conexion::cambiar_modo( $modo ) ) { if ( 'produccion' === $modo ) { self::avisar( 'success', __( 'El sitio volvió a producción.', 'validafirma' ) ); } else { self::avisar( 'success', __( 'El sitio volvió al modo de prueba: no cobra y no sale ningún mensaje.', 'validafirma' ) ); } } else { self::avisar( 'error', __( 'No se pudo cambiar el modo. Si nunca pasaste a producción, usa «Pasar a producción».', 'validafirma' ) ); } self::volver( array( 'page' => 'validafirma-ajustes' ) ); } /** * Desconecta: revoca la llave en ValidaFirma y la borra del sitio. * * @return void */ public static function accion_desconectar() { Validafirma_Permisos::exigir_administradora(); check_admin_referer( 'validafirma_desconectar' ); $resultado = Validafirma_Conexion::desconectar(); if ( is_wp_error( $resultado ) ) { self::avisar( 'error', __( 'No se pudo revocar la llave en ValidaFirma, así que el sitio sigue conectado. Vuelve a intentarlo.', 'validafirma' ) ); self::volver( array( 'page' => 'validafirma-ajustes' ) ); } self::avisar( 'success', __( 'El sitio quedó desconectado. La lista de envíos y los PDF siguen en el sitio.', 'validafirma' ) ); self::volver( array( 'page' => 'validafirma' ) ); } /** * Guarda qué roles pueden usar ValidaFirma. * * @return void */ public static function accion_permisos() { Validafirma_Permisos::exigir_administradora(); check_admin_referer( 'validafirma_permisos' ); $marcados = isset( $_POST['roles'] ) ? array_map( 'sanitize_key', (array) wp_unslash( $_POST['roles'] ) ) : array(); Validafirma_Permisos::aplicar_roles( $marcados ); self::avisar( 'success', __( 'Los permisos quedaron guardados.', 'validafirma' ) ); self::volver( array( 'page' => 'validafirma-ajustes' ) ); } /** * Envía un PDF a firma desde el panel. * * @return void */ public static function accion_enviar() { Validafirma_Permisos::exigir_uso(); check_admin_referer( 'validafirma_enviar' ); $firmantes = isset( $_POST['firmantes'] ) && is_array( $_POST['firmantes'] ) ? map_deep( wp_unslash( $_POST['firmantes'] ), 'sanitize_text_field' ) : array(); $firmantes = array_values( array_filter( $firmantes, 'is_array' ) ); $titulo = isset( $_POST['titulo'] ) ? sanitize_text_field( wp_unslash( $_POST['titulo'] ) ) : ''; $plazo = isset( $_POST['plazo_dias'] ) ? absint( $_POST['plazo_dias'] ) : 7; $confid = ! empty( $_POST['confidencial'] ); $validar = ! empty( $_POST['validar_contacto'] ); $recordar = static function () use ( $firmantes, $titulo, $plazo, $confid, $validar ) { set_transient( 'validafirma_form_' . get_current_user_id(), array( 'firmantes' => $firmantes, 'titulo' => $titulo, 'plazo_dias' => $plazo, 'confidencial' => $confid, 'validar_contacto' => $validar, ), 5 * MINUTE_IN_SECONDS ); }; $fallar = static function ( $mensaje, $recargar = false ) use ( $recordar ) { $recordar(); Validafirma_Admin::avisar( 'error', $mensaje, $recargar ); wp_safe_redirect( admin_url( 'admin.php?page=validafirma-enviar' ) ); exit; }; if ( ! Validafirma_Conexion::conectada() ) { $fallar( Validafirma_Enviador::mensaje_de_motivo( 'desconectado' ) ); } // El PDF: subida válida, PDF de verdad y de hasta 10 MB. $archivo = isset( $_FILES['documento'] ) && is_array( $_FILES['documento'] ) ? $_FILES['documento'] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- se valida abajo campo por campo. if ( empty( $archivo['tmp_name'] ) || UPLOAD_ERR_OK !== (int) $archivo['error'] || ! is_uploaded_file( $archivo['tmp_name'] ) ) { $fallar( __( 'Elige el PDF que quieres enviar a firma.', 'validafirma' ) ); } if ( (int) $archivo['size'] > self::LIMITE_PDF ) { $fallar( __( 'El PDF pesa más de 10 MB.', 'validafirma' ) ); } $nombre_subido = sanitize_file_name( (string) $archivo['name'] ); $cabecera = file_get_contents( $archivo['tmp_name'], false, null, 0, 5 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- archivo subido. if ( ! preg_match( '/\.pdf$/i', $nombre_subido ) || '%PDF-' !== $cabecera ) { $fallar( __( 'El archivo no es un PDF.', 'validafirma' ) ); } if ( ! $firmantes ) { $fallar( __( 'Agrega al menos un firmante.', 'validafirma' ) ); } if ( count( $firmantes ) > 20 ) { $fallar( __( 'Un documento admite hasta 20 firmantes.', 'validafirma' ) ); } $resultado = Validafirma_Enviador::enviar( array( 'origen' => 'panel', 'titulo' => '' !== $titulo ? $titulo : preg_replace( '/\.pdf$/i', '', $nombre_subido ), 'archivo' => $archivo['tmp_name'], 'copiar_archivo' => true, 'firmantes' => $firmantes, 'plazo_dias' => $plazo, 'confidencial' => $confid, 'validar_contacto' => $validar, ), 'manual' ); if ( 'error' === $resultado['estado'] ) { $fallar( $resultado['mensaje'], in_array( $resultado['motivo'], array( 'saldo' ), true ) ); } if ( 'enviando' === $resultado['estado'] ) { self::avisar( 'warning', $resultado['mensaje'] ); } elseif ( Validafirma_Conexion::modo() === 'prueba' ) { self::avisar( 'success', __( 'El documento de prueba quedó creado. No cobra y no sale ningún mensaje: abre «Ver» para simular la firma.', 'validafirma' ) ); } else { self::avisar( 'success', __( 'El documento salió a firma. Cuando se firme, aparecerá aquí para descargarlo.', 'validafirma' ) ); } self::volver( array( 'page' => 'validafirma', 'vista' => Validafirma_Conexion::modo(), ) ); } /** * Cancela un documento pendiente (solo administradores, D-37). * * @return void */ public static function accion_cancelar() { Validafirma_Permisos::exigir_administradora(); $envio = self::envio_de_la_peticion( 'cancelar' ); if ( 'pendiente' !== $envio['estado'] || empty( $envio['documento_id'] ) ) { self::avisar( 'error', __( 'Solo se puede cancelar un documento pendiente.', 'validafirma' ) ); self::volver( array( 'page' => 'validafirma' ) ); } $modo = $envio['es_prueba'] ? 'prueba' : 'produccion'; $respuesta = Validafirma_Cliente_API::instancia()->cancelar_documento( $modo, $envio['documento_id'] ); if ( is_wp_error( $respuesta ) ) { self::avisar( 'error', $respuesta->get_error_message() ); } else { Validafirma_Avisos::aplicar_estado( $envio, 'cancelado' ); self::avisar( 'success', __( 'El documento quedó cancelado. Cancelar no devuelve créditos.', 'validafirma' ) ); } self::volver( array( 'page' => 'validafirma' ) ); } /** * Reenvía la invitación. * * @return void */ public static function accion_reenviar() { Validafirma_Permisos::exigir_uso(); $envio = self::envio_de_la_peticion( 'reenviar' ); if ( 'pendiente' !== $envio['estado'] || empty( $envio['documento_id'] ) ) { self::avisar( 'error', __( 'Solo se puede reenviar la invitación de un documento pendiente.', 'validafirma' ) ); self::volver( array( 'page' => 'validafirma' ) ); } $modo = $envio['es_prueba'] ? 'prueba' : 'produccion'; $respuesta = Validafirma_Cliente_API::instancia()->reenviar_invitacion( $modo, $envio['documento_id'] ); if ( is_wp_error( $respuesta ) ) { self::avisar( 'error', $respuesta->get_error_message() ); } elseif ( $envio['es_prueba'] ) { self::avisar( 'success', __( 'Listo. En modo de prueba no sale ningún mensaje: míralo en el detalle.', 'validafirma' ) ); } elseif ( isset( $respuesta['cuerpo']['enviados'], $respuesta['cuerpo']['omitidos'] ) && array() === $respuesta['cuerpo']['enviados'] && ! empty( $respuesta['cuerpo']['omitidos'] ) ) { self::avisar( 'warning', __( 'No reenviamos la invitación: entre dos envíos al mismo destinatario pasan 5 minutos, y cada firmante admite un reenvío por WhatsApp y cinco por correo.', 'validafirma' ) ); } else { self::avisar( 'success', __( 'Reenviamos la invitación a quienes faltan por firmar.', 'validafirma' ) ); } self::volver( array( 'page' => 'validafirma' ) ); } /** * Envía a mano un «Sin enviar». * * @return void */ public static function accion_enviar_ahora() { Validafirma_Permisos::exigir_uso(); $envio = self::envio_de_la_peticion( 'enviar_ahora' ); $resultado = Validafirma_Enviador::enviar_ahora( $envio['id'] ); if ( 'pendiente' === $resultado['estado'] ) { self::avisar( 'success', __( 'El envío salió a firma.', 'validafirma' ) ); } elseif ( 'enviando' === $resultado['estado'] ) { self::avisar( 'warning', $resultado['mensaje'] ); } else { self::avisar( 'error', $resultado['mensaje'], 'saldo' === $resultado['motivo'] ); } self::volver( array( 'page' => 'validafirma', 'vista' => 'sin_enviar', ) ); } /** * Borra un envío del sitio (solo administradores, D-34). * * @return void */ public static function accion_borrar() { Validafirma_Permisos::exigir_administradora(); $envio = self::envio_de_la_peticion( 'borrar' ); Validafirma_Envios::eliminar( $envio['id'] ); self::avisar( 'success', __( 'Se borró el envío del sitio, con sus datos y la copia de los PDF. La copia de ValidaFirma no se borra desde aquí.', 'validafirma' ) ); self::volver( array( 'page' => 'validafirma' ) ); } /** * Simula un desenlace en un documento de prueba (D-25, D-37). * * @return void */ public static function accion_simular() { Validafirma_Permisos::exigir_uso(); $envio = self::envio_de_la_peticion( 'simular' ); $destino = array( 'page' => 'validafirma', 'accion' => 'detalle', 'envio' => $envio['id'], ); if ( ! $envio['es_prueba'] || 'pendiente' !== $envio['estado'] || empty( $envio['documento_id'] ) ) { self::avisar( 'error', __( 'Las simulaciones son solo para documentos de prueba que siguen pendientes.', 'validafirma' ) ); self::volver( $destino ); } $que = isset( $_REQUEST['simulacion'] ) ? sanitize_key( wp_unslash( $_REQUEST['simulacion'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification -- verificado en envio_de_la_peticion(). $firmante = isset( $_REQUEST['firmante'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['firmante'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification -- idem. $api = Validafirma_Cliente_API::instancia(); if ( 'vencer' === $que ) { $respuesta = $api->simular_vencimiento( $envio['documento_id'] ); } elseif ( in_array( $que, array( 'firmar', 'rechazar' ), true ) && '' !== $firmante ) { $respuesta = $api->simular( $envio['documento_id'], $firmante, 'firmar' === $que ? 'firmar' : 'rechazar-documento' ); } else { self::avisar( 'error', __( 'Esa simulación no existe.', 'validafirma' ) ); self::volver( $destino ); } if ( is_wp_error( $respuesta ) ) { self::avisar( 'error', $respuesta->get_error_message() ); self::volver( $destino ); } // El desenlace llega por aviso si el sitio es público; aquí se consulta para verlo de inmediato. Una firma // simulada cierra el documento en ValidaFirma unos segundos después: se espera hasta 8 segundos. delete_transient( 'validafirma_det_' . $envio['id'] ); $intentos = 'firmar' === $que ? 8 : 1; for ( $i = 1; $i <= $intentos; $i++ ) { $cuerpo = Validafirma_Revision::revisar_envio( $envio ); $estado = ! is_wp_error( $cuerpo ) && isset( $cuerpo['documento']['estado'] ) ? (string) $cuerpo['documento']['estado'] : ''; if ( 'pendiente' !== $estado || $i === $intentos ) { break; } sleep( 1 ); } if ( 'pendiente' === $estado ) { // Sigue abierto: que la próxima revisión lo vea sin esperar los 15 minutos de espacio. Validafirma_Envios::actualizar( $envio['id'], array( 'ultima_revision_en' => null ) ); } delete_transient( 'validafirma_det_' . $envio['id'] ); self::avisar( 'success', __( 'Listo: simulaste el desenlace en el documento de prueba.', 'validafirma' ) ); self::volver( $destino ); } /** * Descarga un PDF guardado en el almacén privado (solo con permiso; nunca por una dirección pública, RN-06). * * @return void */ public static function accion_descargar() { // Descarga de solo lectura: sin nonce, porque el enlace se guarda permanente (por ejemplo en una nota de la // entrada de WPForms). Basta la sesión iniciada con el permiso, y que el envío y el archivo existan. Validafirma_Permisos::exigir_uso(); // phpcs:disable WordPress.Security.NonceVerification -- ver el comentario de arriba. $id = isset( $_GET['envio'] ) ? absint( $_GET['envio'] ) : 0; $tipo = isset( $_GET['tipo'] ) && 'original' === sanitize_key( wp_unslash( $_GET['tipo'] ) ) ? 'original' : 'firmado'; // phpcs:enable $envio = $id ? Validafirma_Envios::obtener( $id ) : null; if ( ! $envio ) { wp_die( esc_html__( 'Ese envío ya no existe.', 'validafirma' ), '', array( 'response' => 404 ) ); } $ruta = Validafirma_Almacen_Privado::ruta( (string) ( 'original' === $tipo ? $envio['archivo_original'] : $envio['archivo_firmado'] ) ); if ( '' === $ruta ) { wp_die( esc_html__( 'El PDF ya no está en el sitio.', 'validafirma' ), '', array( 'response' => 404 ) ); } $nombre = sanitize_file_name( '' !== $envio['titulo'] ? $envio['titulo'] : 'documento' ); $nombre = preg_replace( '/\.pdf$/i', '', $nombre ) . ( 'firmado' === $tipo ? '-firmado' : '' ) . '.pdf'; nocache_headers(); header( 'Content-Type: application/pdf' ); header( 'Content-Disposition: attachment; filename="' . $nombre . '"' ); header( 'Content-Length: ' . filesize( $ruta ) ); header( 'X-Content-Type-Options: nosniff' ); readfile( $ruta ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- entrega controlada de un archivo privado. exit; } }