'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 '
 . ')
';
}
/**
* 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 '
';
}
/**
* 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' ) . '
';
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;
}
}