$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; /** * Crea un nuevo formulario. * * Posibles opciones: *
Opción | *Valor por defecto | *Descripció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. | *
$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( 'action' => null, 'class' => null, 'enctype' => null );
$opciones = array_merge($opcionesPorDefecto, $opciones);
$this->action = $opciones['action'];
$this->classAtt = $opciones['class'];
$this->enctype = $opciones['enctype'];
if ( !$this->action ) {
$this->action = htmlentities($_SERVER['PHP_SELF']);
}
}
/**
* Se encarga de orquestar todo el proceso de gestión de un formulario.
*
* El proceso es el siguiente:
* string
en {@see Form::procesaFormulario()}
* que será la URL a la que se rederigirá al usuario. Se redirige al usuario y se termina la ejecución del script.array
con entradas (campo, mensaje) con errores específicos para un campo o (entero, mensaje) si el mensaje
* es un mensaje que afecta globalmente al formulario. Se vuelve a generar el formulario pasándole el array de errores.$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 = "";
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 = "$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]}$htmlElement>";
}
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)));
}
}