$tipoFormulario.$formId. */ private $formId; /** * @var string Valor del parámetro enctype del formulario. */ private $enctype; /** * @var string Valor del atributo "class" de la etiqueta <form> asociada al formulario. Si este parámetro incluye la cadena "nocsrf" no se generá el token CSRF para este formulario. */ private $classAtt; /** * @var string Parámetro de la petición utilizado para comprobar que el usuario ha enviado el formulario.. */ private $tipoFormulario; /** * @var string URL asociada al atributo "action" de la etiqueta <form> del fomrulario y que procesará el * envío del formulario. */ private $action; private $printed; /** * @var bool Almacena si la interacción con el formulario va a realizarse a través de AJAX true o * false en otro caso. */ private $ajax; /** * Crea un nuevo formulario. * * Posibles opciones: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
OpciónValor por defectoDescripción
action$_SERVER['PHP_SELF']URL asociada al atributo "action" de la etiqueta <form> del fomrulario y que procesará el envío del formulario.
class""Valor del atributo "class" de la etiqueta <form> asociada al formulario. Si este parámetro incluye la cadena * "nocsrf" no se generá el token CSRF para este formulario.
enctype""Valor del parámetro enctype del formulario.
ajaxfalseConfigura si el formulario se gestionará a través de AJAX.
* @param string $tipoFormulario Parámetro de la petición utilizado para comprobar que el usuario ha enviado el formulario. * @param string $formId (opcional) Identificador utilizado para construir el atributo "id" de la etiqueta <form> como $tipoFormulario.$formId. * * @param array $opciones (ver más arriba). */ public function __construct($tipoFormulario, $opciones = array(), $formId = 1) { $this->tipoFormulario = $tipoFormulario; $this->formId = $tipoFormulario.$formId; $opcionesPorDefecto = array( 'ajax' => false, 'action' => null, 'class' => null, 'enctype' => null ); $opciones = array_merge($opcionesPorDefecto, $opciones); $this->ajax = $opciones['ajax']; $this->action = $opciones['action']; $this->classAtt = $opciones['class']; $this->enctype = $opciones['enctype']; if ( !$this->action ) { // Cambiar por << $this->action = htmlentities($_SERVER['REQUEST_URI']); >> para mantener los parámetros de la URL. $this->action = htmlentities($_SERVER['PHP_SELF']); } } /** * Se encarga de orquestar todo el proceso de gestión de un formulario. * * El proceso es el siguiente: * */ public function gestiona() { if ( ! $this->formularioEnviado($_POST) ) { return $this->generaFormulario(); } else { // Valida el token CSRF si es necesario (hay un token en la sesión asociada al formulario) $tokenRecibido = $_POST['CSRFToken'] ?? FALSE; $errores = $this->csrfguard_ValidateToken($this->tipoFormulario, $tokenRecibido); // limpia los tokens CSRF que no han sido utilizados en esta petición self::limpiaCsrfTokens(); // Sin AJAX. /** * $result = $this->procesaFormulario($_POST); * if ( is_array($result) ) { * return $this->generaFormulario($_POST, $result); * } else { * header('Location: '.$result); * exit(); * } */ // Con AJAX. if ( $errores !== TRUE ) { if ( ! $this->ajax ) { return $this->generaFormulario($_POST, $errores); } else { return $this->generaHtmlErrores($errores); } } else { $result = $this->procesaFormulario($_POST); if ( is_array($result) ) { // Error al procesar el formulario, volvemos a mostrarlo if ( ! $this->ajax ) { return $this->generaFormulario($_POST, $result); } else { return $this->generaHtmlErrores($result); } } else { if ( ! $this->ajax ) { header('Location: '.$result); exit(); } else { return $result; } } } } } /** * Genera el HTML necesario para presentar los campos del formulario. * * Si el formulario ya ha sido enviado y hay errores en {@see Form::procesaFormulario()} se llama a este método * nuevamente con los datos que ha introducido el usuario en $datosIniciales y los errores al procesar * el formulario en $errores * * @param string[] $datosIniciales Datos iniciales para los campos del formulario (normalmente $_POST). * * @param string[] $errores (opcional)Lista / Tabla asociativa de errores asociados al formulario. * * @return string HTML asociado a los campos del formulario. */ protected function generaCamposFormulario($datosIniciales, $errores = array()) { return ''; } /** * Procesa los datos del formulario. * * @param string[] $datos Datos enviado por el usuario (normalmente $_POST). * * @return string|string[] Devuelve el resultado del procesamiento del formulario, normalmente una URL a la que * se desea que se redirija al usuario, o un array con los errores que ha habido durante el procesamiento del formulario. */ protected function procesaFormulario($datos) { return array(); } /** * Función que verifica si el usuario ha enviado el formulario. * * Comprueba si existe el parámetro $formId en $params. * * @param string[] $params Array que contiene los datos recibidos en el envío formulario. * * @return boolean Devuelve true si $formId existe como clave en $params */ private function formularioEnviado(&$params) { return isset($params['action']) && $params['action'] == $this->tipoFormulario; } /** * Función que genera el HTML necesario para el formulario. * * @param string[] $datos (opcional) Array con los valores por defecto de los campos del formulario. * * @param string[] $errores (opcional) Array con los mensajes de error de validación y/o procesamiento del formulario. * * @return string HTML asociado al formulario. */ private function generaFormulario(&$datos = array(), &$errores = array()) { $htmlCamposFormularios = $this->generaCamposFormulario($datos, $errores); $classAtt=''; if ( $this->classAtt ) { $classAtt = " class=\"{$this->classAtt}\""; } $enctypeAtt=''; if ( $this->enctype ) { $enctypeAtt = " enctype=\"{$this->enctype}\""; } // Se genera el token CSRF si el usuario no solicita explícitamente lo contrario. $tokenCSRF = ''; if ( ! $this->classAtt || strpos($this->classAtt, 'nocsrf') === false ) { $tokenValue = $this->csrfguard_GenerateToken($this->tipoFormulario); $tokenCSRF = ""; } /* <<< Permite definir cadena en múltiples líneas. * Revisa https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc */ $htmlForm = "
".$tokenCSRF.$htmlCamposFormularios."
"; return $htmlForm; } /** * Genera la lista de mensajes de errores globales (no asociada a un campo) a incluir en el formulario. * * @param string[] $errores (opcional) Array con los mensajes de error de validación y/o procesamiento del formulario. * * @param string $classAtt (opcional) Valor del atributo class de la lista de errores. * * @return string El HTML asociado a los mensajes de error. */ protected static function generaListaErroresGlobales($errores = array(), $classAtt='') { $html=''; $clavesErroresGenerales = array_filter(array_keys($errores), function ($elem) { return is_numeric($elem); }); $numErrores = count($clavesErroresGenerales); if ($numErrores > 0) { $html = "'; } return $html; } /** * Crea una etiqueta para mostrar un mensaje de error. Sólo creará el mensaje de error * si existe una clave $idError dentro del array $errores. * * @param string[] $errores (opcional) Array con los mensajes de error de validación y/o procesamiento del formulario. * @param string $idError (opcional) Clave dentro de $errores del error a mostrar. * @param string $htmlElement (opcional) Etiqueta HTML a crear para mostrar el error. * @param array $atts (opcional) Tabla asociativa con los atributos a añadir a la etiqueta que mostrará el error. */ protected static function createMensajeError($errores=array(), $idError='', $htmlElement='span', $atts = array()) { $html = ''; if (isset($errores[$idError])) { $att = ''; foreach($atts as $key => $value) { $att .= "$key=$value"; } $html = " <$htmlElement $att>{$errores[$idError]}"; } return $html; } /** * Método para eliminar los tokens CSRF almecenados en la petición anterior que no hayan sido utilizados en la actual. */ public static function limpiaCsrfTokens() { foreach(array_keys($_SESSION) as $key) { if (strpos($key, self::CSRF_PARAM) === 0) { unset($_SESSION[$key]); } } } private function csrfguard_GenerateToken($formParameter) { if ( ! session_id() ) { throw new \Exception('La sesión del usuario no está definida.'); } $paramSession = self::CSRF_PARAM.'_'.$formParameter; if (isset($_SESSION[$paramSession])) { $token = $_SESSION[$paramSession]; } else { if ( function_exists('hash_algos') && in_array('sha512', hash_algos()) ) { $token = hash('sha512', mt_rand(0, mt_getrandmax())); } else { $token=' '; for ($i=0;$i<128;++$i) { $r=mt_rand(0,35); if ($r<26){ $c=chr(ord('a')+$r); } else{ $c=chr(ord('0')+$r-26); } $token.=$c; } } $_SESSION[$paramSession]=$token; } return $token; } private function csrfguard_ValidateToken($formParameter, $tokenRecibido) { if ( ! session_id() ) { throw new \Exception('La sesión del usuario no está definida.'); } $result = TRUE; $paramSession = self::CSRF_PARAM.'_'.$formParameter; if ( isset($_SESSION[$paramSession]) ) { if ( $_SESSION[$paramSession] !== $tokenRecibido ) { $result = array(); $result[] = 'Has enviado el formulario dos veces'; } $_SESSION[$paramSession] = ' '; unset($_SESSION[$paramSession]); } else { $result = array(); $result[] = 'Formulario no válido'; } return $result; } //Test some form input. protected function test_input($input){ return htmlspecialchars(trim(strip_tags($input))); } }