Códigos del formulario y proceso de validación
Estos códigos se corresponden con ejemplos que se exponen en los capítulos V y VI, sobre la validación de datos recibidos de formularios.
form1.php
<?php /* Ejemplo de un script PHP para validar formulario * Andrés de la Paz © 2010 * http://www.wextensible.com */ //Necesitamos sólo cookies para la sesión ini_set("session.use_cookies", 1); ini_set("session.use_only_cookies", 1); ini_set("session.use_trans_sid", 0); //Damos un nombre a estas sesiones e iniciamos sesión define("SESION", "ejemploValidaForm1"); session_name(SESION); session_start(); //Esta constante da el nombre al campo identificador de formulario. define("IDENTIFICAFORM", "identifica-form1"); //Comprobamos si la sesión ya fue iniciada $hay_sesion = (isset($_SESSION["ident1"]) && isset($_SESSION["ident2"])); //Comprobar si entramos con POST $hay_post = isset($_POST[IDENTIFICAFORM]); //En la página hay un segundo formulario con un único botón para finalizar //la sesión y reiniciar el formulario de nuevo $finaliza_sesion = isset($_POST["fin-sesion"]); //para identificar el usuario en "ident1" $usuario_identificado = false; //para identificar el formulario en "ident2" $form_identificado = false; //Para detectar la primera vez que entramos en el formulario $hay_regeneracion = false; //Nos dice si los campos del formulario se han validado correctamente $form_validado = false; //En caso de validarse se mantiene "", en otro caso contiene un mensaje con //los errores de validación $mensaje_validar = ""; //Incluimos aquí el script que valida el formulario include "valida-form.php"; //Array con la definición y valores de campos $array_campos = array( IDENTIFICAFORM => array( "valor" => "", "titulo" => IDENTIFICAFORM, "tipo" => IDENTIFICAFORM, "longitud-min" => 32, "longitud-max" => 32, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "hidden", "class" => PREFIJOVAL ) ), "nombre" => array( "valor" => "", "titulo" => "Nombre", "tipo" => "nombre de persona", "longitud-min" => 0, "longitud-max" => 40, "requerido" => false, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "user" => array( "valor" => "", "titulo" => "Usuario", "tipo" => "usuario", "longitud-min" => 4, "longitud-max" => 10, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "pwd" => array( "valor" => "", "titulo" => "Contraseña", "tipo" => "contraseña", "longitud-min" => 4, "longitud-max" => 10, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "password", "class" => PREFIJOVAL ) ) ); //Construimos el array de patrones para esta definición contruir_array_patrones($array_campos); //Iniciamos el proceso de control de sesión if (!$hay_sesion || ($hay_sesion && !$hay_post) || $finaliza_sesion) { //Con sesiones no iniciadas, iniciadas sin post o finalizadas $hay_regeneracion = true; session_regenerate_id(true); $_SESSION["ident1"] = md5($_SERVER["HTTP_USER_AGENT"]); $_SESSION["ident2"] = md5(uniqid(rand(), true)); //Guardamos el valor del identificador de formulario en el array $array_campos[IDENTIFICAFORM]["valor"] = $_SESSION["ident2"]; } else {//Con sesiones iniciadas y con POST //¿es el mismo usuario? if (isset($_SERVER["HTTP_USER_AGENT"])) $usuario_identificado = ($_SESSION["ident1"] == md5($_SERVER["HTTP_USER_AGENT"])); //Si se identificó el navegador, vemos si hay POST recibido if ($usuario_identificado){ //Recibimos los campos y nos devuelve si se identificó el formulario $form_identificado = recibir_campos($_POST, $array_campos, $_SESSION["ident2"]); if ($form_identificado){ //Validamos los campos del formulario $mensaje_validar = validar_form($array_campos); if ($mensaje_validar == "") { //Si no hay mensajes es que el formulario es válido $form_validado = true; //En ese caso destruimos la sesión actual y la cookie del navegador //pues los datos validados serán enviados a otro proceso (aunque //en este ejemplo se limita a presentarlos otra vez $_SESSION = array(); if (ini_get("session.use_cookies")) { $params = session_get_cookie_params(); setcookie(SESION, '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"] ); } session_destroy(); //Aquí iría el proceso de destinar los datos validados en $array_campos //a algún sitio (archivo, base de datos, etc) y luego redirigiría a //algúna otra página para seguir algún otro proceso. En este ejemplo //volvemos a presentar esta página presentando los datos en pantalla. } } } } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es"> <head> <title>Validar formulario</title> <meta http-equiv="X-UA-Compatible" content="IE=8" /> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <meta name="author" content="Andrés de la Paz" /> <meta name="copyright" content="© 2010" /> <link rel="stylesheet" type="text/css" href="/res/sty/formatos.css" /> <script type="text/javascript" src="/res/inc/general.js" charset="ISO-8859-1"></script> <script type="text/javascript" src="valida-form.js" charset="UTF-8"></script> <?php if (!$form_validado) echo construir_array_js(); ?> <script> //Esta función rellena ejemplos en los campos para probar la validación function rellenarEjemplo(){ try { var formulario = document.forms[0]; if (formulario != null) { formulario["nombre"].value = "Juan"; formulario["user"].value = "usuario"; formulario["pwd"].value = "a12_B"; } } catch (e) {} } </script> </head> <body> <a href="http://www.wextensible.com"><img src="/res/img/wextensible.gif" width="41" height="40" style="border: gray solid 1px;" alt="wextensible" title="www.wextensible.com" /></a> <h3>Validación de formulario 1</h3> <?php if ((!$form_identificado)||($form_identificado && !$form_validado)) { ?> <p>Se necesitan cookies para recibir este formulario.</p> <noscript> <p class="verde">Se recomienda activar Javascript para facilitar el uso de este formulario.</p> </noscript> <?php if (!$form_identificado && !$hay_regeneracion){ echo "<p style='color: red'>El formulario no se pudo identificar. Por ". "favor, reinicie el formulario.</p>"; } else { echo construir_form($array_campos, $mensaje_validar); } ?> <form action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="post"> <input type="submit" name="fin-sesion" value="reiniciar formulario" /> </form> <?php } else { echo "<p style='color: green'>SE PROCESAN LOS DATOS RECIBIDOS:</p>"; //Se procesan los datos solo si se identificó el usuario y el formulario ?> <p>En este ejemplo se presentan los datos en esta misma página, pero en un caso real quizás se enviarían los datos a otro proceso. La siguiente lista se obtiene con la función <code>listar_campos()</code>: </p> <?php echo listar_campos($array_campos, "ol"); ?> <p>También podemos obtener un array de campos con la función <code>extraer_valores()</code>. Así podemos manejar este array y pasarlo a algún script que trate sus valores, en lugar de manipular el array <code>$_POST</code>: </p> <pre><?php echo print_r(extraer_valores($array_campos)); ?></pre> <?php } ?> <br /> <!-- Este código dentro de este div sólo es para explicar el proceso --> <div style="color: gray; border: gray solid 1px;"> <?php if (!$form_validado) { ?> <h3>Utilidades para probar este ejemplo</h3> <p><input type="checkbox" id="no-valida-js" /> Podemos saltar la validación en JavaScript en el navegador del usuario con esta casilla de verificación</p> <p><input type="button" value="rellenar ejemplo" onclick="rellenarEjemplo('form-validar-1')" /> Con este botón rellenamos los campos con un ejemplo para probar. </p> <?php } ?> <h3>Valores de configuración</h3> <ul> <li>Nombre sesión <code>session_name()</code>: <code class="verde"> <?php echo session_name(); ?> </code></li> <li>Identificador sesión <code>session_id()</code>: <code class="verde"> <?php echo session_id(); ?> </code></li> <li>Cookie con el identificador de sesión <code>$_COOKIE["<?php echo SESION; ?>"]</code>: <code class="verde"> <?php if (isset($_COOKIE[SESION])) { echo htmlspecialchars($_COOKIE[SESION]); } else { echo "<span class='rojo'>NO EXISTE</span>"; } ?> </code></li> <li>Identificador navegador usuario <code>$_SESSION["ident1"]</code>:<code class="verde"> <?php if (isset($_SESSION["ident1"])) { echo $_SESSION["ident1"]; } else { echo "<span class='rojo'>NO EXISTE</span>"; } ?> </code></li> <li>Navegador del usuario <code>$_SERVER["HTTP_USER_AGENT"]</code>:<code class="verde"> <?php if (isset($_SERVER["HTTP_USER_AGENT"])) { echo $_SERVER["HTTP_USER_AGENT"]; } else { echo "<span class='rojo'>NO EXISTE</span>"; } ?> </code></li> <li>Identificador formulario <code>$_SESSION["ident2"]</code>:<code class="verde"> <?php if (isset($_SESSION["ident2"])) { echo $_SESSION["ident2"]; } else { echo "<span class='rojo'>NO EXISTE</span>"; } ?> </code></li> </ul> <h3>Cookies en el navegador</h3> <p>Busca en el navegador con JavaScript cookies con nombres <code>"<?php echo SESION; ?>"</code>. </p> <code id="mi-cookie" style="border: red solid 1px;"> </code> <script> var arrayCookies = document.cookie.split(";"); var contenido = ""; for (var i=0; i<arrayCookies.length; i++){ var unaCookie = arrayCookies[i].split("="); var nombre = quitaEspacios(unaCookie[0]); if (nombre == "<?php echo SESION; ?>") { contenido += escapaHtml(arrayCookies[i]) + "; "; } } if (contenido == "") contenido = "No hay cookies en el navegador."; setInnerText(document.getElementById("mi-cookie"), contenido); </script> <br /> </div> </body> </html>
valida-form.php
<?php /* Script PHP para validar campos de formulario * Andrés de la Paz © 2010 * http://www.wextensible.com */ /* Constante para incluir un asterisco u otra cosa para indicar los campos * de formulario que son requeridos. Si no se desea, poner un campo vacío "". */ define("ASTERISCO", "<span style=\"color:red\">*</span>"); /* Un caracter para separar la cadena tipo#[requerido|opcional]#longitud en el title del * campo al construir el HTML. No debe ser un espacio, pues el tipo puede * ser una frase para comunicarle al usuario el tipo de campo que se espera. */ define("SEPARA", "#"); /* Esta cadena la incorporaremos en la matriz de definición de campos dentro del * atributo "class" de los elementos que se construirán dinámicamente. Los campos * que lleven este valor de atributo serán validados. */ define("PREFIJOVAL", "val-"); /* El array con los patrones base para validar los campos. El tipo puede ser una frase * pero no debe contener el carácter señalado en la constante SEPARA. Es obvio que * el tipo es un clave para indexar el array, por lo que no habrán duplicados. * En los patrones deben escaparse las barras invertida y las comillas y NO deben * añadirse los caracteres de inicio y fin de patrón (usualmente las barras derecha). */ $patrones_tipos_base = array( IDENTIFICAFORM => array( "patron" => "^[a-zA-Z0-9]+$", "mensaje" => "Identificador de formulario no válido."), "número natural" => array( "patron" => "^(?:0|[1-9]\\d*)$", "mensaje" => "Debe ser un número natural (no negativo)."), "número entero" => array( "patron" => "^(?:0|\\-?[1-9]\\d*)$", "mensaje" => "Debe ser un número entero."), "número real" => array( "patron" => "^(?:0|\\-?[1-9]\\d*)(?:\\.\\d+)?$", "mensaje" => "Debe ser un número natural, entero o real."), "usuario" => array( "patron" => "^\\w+$", "mensaje" => "Un nombre de usuario debe contener letras a-z, A-Z, dígitos ". "0-9 o guión bajo."), "contraseña" => array( "patron" => "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])\\w+$", "mensaje" => "Una contraseña debe contener de letras a-z, A-Z, dígitos 0-9 ". "o guión bajo, pero debe tener al menos 1 letra minúscula, 1 mayúscula y 1 dígito."), "DNI" => array( "patron" => "^[1-9]\d*[A-Z]$", "mensaje" => "Debe contener de 1 a 8 dígitos numéricos y una letra mayúscula."), "teléfono" => array( "patron" => "^\\d+$", "mensaje" => "Debe contener dígitos numéricos."), "email" => array( "patron" => "^\\w+(?:[\\-\\.]?\\w+)*@\\w+(?:[\\-\\.]?\\w+)*(?:\\.[a-zA-Z]{2,4})+$", "mensaje" => "Debe ser de la forma 'xx@xx.yy' donde en 'xx' se permiten dígitos ". "númericos, letras, guiones y puntos, mientras que en 'yy' debe contener uno o más ". "dominios separados por punto, pero cada uno de ellos debe tener entre 2 y 4 letras."), "verificación" => array( "patron" => "^si$", "mensaje" => "Casilla de verificación con un valor no esperado."), "nombre de persona" => array( "patron" => "^[A-ZÑÇÁÉÍÓÚ][a-zA-ZñÑçÇáéíóúüÁÉÍÓÚÜ \\-]+$", "mensaje" => "Un nombre de persona empieza con mayúscula y debe contener letras ". "a-zA-ZñÑçÇáéíóúüÁÉÍÓÚÜ, espacios o guiones -."), "texto simple" => array( "patron" => "^[\\wñÑçÇáéíóúüÁÉÍÓÚÜ \\-\\.\\,\\?¿\\!¡]*$", "mensaje" => "Debe contener texto limitado a las letras a-z o A-Z incluyendo ". "ñ, Ñ, ç, Ç y vocales con tildes y los signos _-.,¿?¡!"), "texto Latín-B" => array( "patron" => "^[ -ɏ]*$", "mensaje" => "El texto debe contener caracteres permitidos hasta Latín extendido B (u0020-u024F)") ); /* Los patrones base anteriores son todos los que existen. Para el uso del formulario * donde se aplique, se extraerán sólo los que se necesiten usando la función * construir_array_patrones() */ $patrones_tipos = array(); /* Construye un array de patrones a usar en el formulario definido en * el array del argumento $array_campos. Se extraen del array de * patrones base ($array_patrones_base) usando sólo los que se * necesiten. Así evitamos manejar un array muy grande y, sobre todo, * no traspasamos a Javascript aquellos que no se usarán. */ function contruir_array_patrones($array_campos){ global $patrones_tipos_base; global $patrones_tipos; $arr_tipos = array(); foreach ($array_campos as $arr){ $tipo = $arr["tipo"]; if (($tipo != "valor")&&($tipo != "selector")&& ($tipo != "menú")&& (!in_array($arr["tipo"], $arr_tipos))){ $arr_tipos[] = $arr["tipo"]; } } foreach ($arr_tipos as $tipo){ $patron = $patrones_tipos_base[$tipo]["patron"]; $mensaje = $patrones_tipos_base[$tipo]["mensaje"]; $patrones_tipos[$tipo] = array("patron"=>$patron, "mensaje"=>$mensaje); } } /* Esta función nos permite construir un elemento <script> para incluir en el HTML * con el array $patrones_tipos para validar con JavaScript. */ function construir_array_js(){ global $patrones_tipos; $cadena = ""; foreach ($patrones_tipos as $tipo => $arr){ if ($cadena != "") $cadena .= ","; $cadena .= "\"".$tipo."\",/".$arr["patron"]."/,\"".$arr["mensaje"]."\""; } $cadena = "<script type=\"text/javascript\">\r\n". "//<![CDATA[\r\n". "var patronesTipos = Array(".$cadena.");\r\n". "//]]>\r\n". "</script>\r\n"; return $cadena; } /* Con esta función recibimos los campos desde $_POST a través del primer * argumento $array_post. En $array_campos, que se pasa por referencia * pues podrían darse modificaciones, pasamos el array con la definición * de campos. En $ident_form pasamos el identificador de formulario que * está en una variable de sesión $_SESSION para identificar el * formulario con un campo definido en $array_campos que se llama * como la constante IDENTIFICAFORM. */ function recibir_campos($array_post, &$array_campos, $ident_form){ //Esta variable será devuelta al final para indicar si se //identificó el formulario $form_identificado = false; //Vemos si tiene magic_quotes activado el servidor $magicq = (get_magic_quotes_gpc() == 1); //Array temporal para control de casillas de verificación (checkbox) $array_check = array(); foreach($array_post as $campo=>$valor){ $un_campo = htmlspecialchars($campo, ENT_QUOTES); if ($magicq) $un_campo = stripslashes($un_campo); $un_valor = htmlspecialchars($valor, ENT_QUOTES); if ($magicq) $un_valor = stripslashes($un_valor); //Vemos si el nombre del campo está en las entradas del array de //definiciones de campos $array_campos if (array_key_exists($un_campo, $array_campos)){ //Buscamos el campos identificador de formulario. Si el valor //no coincide con el de la sesión, damos por no identificado al //formulario y salimos inmediatamente del bucle. if ($un_campo == IDENTIFICAFORM){ $form_identificado = ($un_valor == $ident_form); if (!$form_identificado) break; } //Por seguridad, se cortan los datos al doble de lo esperado. Luego //se pasará la validación y se comprueba si tienen la longitud esperada. $longitud_doble = 2 * $array_campos[$un_campo]["longitud"]; $array_campos[$un_campo]["valor"] = substr($un_valor, 0, $longitud_doble); //Si el campo recibido es una casilla de verificación, la guardamos en //un array temporal al mismo tiempo que ponemos el atributo "checked" if (($array_campos[$un_campo]["tag"] == "input")&& ($array_campos[$un_campo]["atributos"]["type"] == "checkbox")){ //Guarda en array temporal los checkbox recibidos $array_check[] = $un_campo; //Si llega un checkbox, es que se envió pulsado $array_campos[$un_campo]["atributos"]["checked"] = "checked"; } } else if (($campo != "envio")&&($valor != "enviar")) { //Si se recibe algún campo no esperado (que no esté en el array de //definiciones), inmediatamente damos por no identificado el formulario. //A no ser que el usuario haya desactivado JavaScript, lo que causará que //se remita también el botón de envío, botón que no se recibe con Javascript //debido a que el form tiene un onsubmit que lo omite. De todas formas //en este caso sólo permitiremos este campo "extra". $form_identificado = false; break; } } //Ahora repasamos el array temporal de casillas de verificación para analizar //cuáles no se recibieron y en consecuencia desactivaríamos el atributo "checked" foreach ($array_campos as $campo => $arr){ if (($arr["tag"] == "input") && ($arr["atributos"]["type"] == "checkbox") && (!in_array($campo, $array_check))){ $array_campos[$campo]["atributos"]["checked"] = ""; } } //Devolvemos este booleano que indica si se identificó el formulario return $form_identificado; } /* Aquí validamos los campos del formulario. Tomamos el array $patrones_tipos * definido como variable global e iteramos por $array_campos donde traemos * el array de definición de campos, que también contiene los valores, para * entonces proceder a validarlos con los patrones. Pasamos el array por valor * pues aquí no necesitamos modificarlo. * Recordemos que el tipo "selector" es para elementos <input type="radio"> y * que el tipo "menú" es para elementos <select><option>...</select> */ function validar_form($array_campos){ //En esta variable está la lista de patrones global $patrones_tipos; //Aquí concatenaremos todos los errores $mensaje = ""; //Iteramos por el array de campos que contiene tipos y valores foreach ($array_campos as $campo => $arr){ //Mensaje de longitud $mensaje_longitud = "("; if ($arr["longitud-max"] == $arr["longitud-min"]){ $mensaje_longitud .= $arr["longitud-max"]; } else { $mensaje_longitud .= "de ".$arr["longitud-min"]." a ".$arr["longitud-max"]; } $mensaje_longitud .= " caracteres)"; //Si la clase es PREFIJOVAL, no se validará el campo if ($arr["atributos"]["class"] == PREFIJOVAL){ //Si el tipo no es "valor", "selector" o "menú", entonces debe //estar entre los tipos de la lista de patrones if (($arr["tipo"] != "valor") && ($arr["tipo"] != "selector") && ($arr["tipo"] != "menú") && (!array_key_exists($arr["tipo"], $patrones_tipos))){ $mensaje .= "<li>Falta el tipo "."'".$arr["tipo"]. "' para el campo '".$arr["titulo"]."'.</li>"; } else { //Verificamos la longitud del campo $longitud = strlen($arr["valor"]); if (($longitud < $arr["longitud-min"]) || ($longitud > $arr["longitud-max"])) { $mensaje .= "<li>El tamaño del valor del campo '".$arr["titulo"]."' es "; if ($longitud > $arr["longitud-max"]) { $mensaje .= "mayor que el esperado ".$mensaje_longitud.".</li>"; } else { $mensaje .= "menor que el esperado ".$mensaje_longitud.".</li>"; } } else { //Si el tipo es "selector" o "menú", verificamos que el //valor seleccionado está en la lista de opciones if ((($arr["tipo"] == "selector")||($arr["tipo"] == "menú")) && (!in_array($arr["valor"], $arr["opciones"]))){ $mensaje .= "<li>El campo '".$arr["titulo"]. "' contiene un valor no esperado '".$arr["valor"]. "' ".$mensaje_longitud."</li>"; } //Verificamos si el valor es requerido y que no esté vacío if ($arr["requerido"] && ($arr["valor"] == "")){ $mensaje .= "<li>El valor del campo '".$arr["titulo"]. "' es requerido ".$mensaje_longitud."</li>"; } else if (($arr["tipo"] != "valor") && ($arr["tipo"] != "selector") && ($arr["tipo"] != "menú") && ($arr["requerido"] || (!$arr["requerido"] && $arr["valor"] != ""))){ //Verificamos el tipo con los patrones $patron = "/".$patrones_tipos[$arr["tipo"]]["patron"]."/"; if (!preg_match($patron, $arr["valor"])){ $mensaje .= "<li>Error en el campo '".$arr["titulo"]."'". ": ".$patrones_tipos[$arr["tipo"]]["mensaje"]. " ".$mensaje_longitud."</li>"; } } } } } } //Si hay entradas de mensaje, componemos el resto if ($mensaje != "") { $mensaje = "<p>Hay errores con la validación de este formulario:</p>". "<ol>$mensaje</ol>"; } //Devolvemos el mensaje, vacío o lleno. Si está vacío es que NO //se produjeron errores en la validación. return $mensaje; } /* Usando el array de definiciones y valores de campos $array_campos (por valor) * procedemos a construir los campos interiores al elemento <form>. * Recordemos que el tipo "selector" es para elementos <input type="radio"> y * que el tipo "menú" es para elementos <select><option>...</select> */ function construir_form($array_campos){ //En esta cadena vamos concatenando todo el literal HTML $cadena = "<form action=\"".$_SERVER["PHP_SELF"]."\" method=\"post\" ". "onsubmit=\"return validaForm(this)\">"; ; foreach ($array_campos as $campo => $arr){ //contenedor que encierra cada campo $contenedor = "label"; $atributos_contenedor = ""; //elemento que encierra el texto de la etiqueta de cada campo $etiqueta = "span"; if ($arr["tipo"] == "selector"){ //Para los <input type="radio"> el contenedor es un <fieldset> $contenedor = "fieldset"; if ($arr["estilo-fieldset"] != ""){ $atributos_contenedor .= " style=\"".$arr["estilo-fieldset"]."\""; } if ($arr["clase-fieldset"] != "") { $atributos_contenedor .= " class=\"".$arr["clase-fieldset"]."\""; } //La etiqueta serán un <legend> propio del <fieldset> $etiqueta = "legend"; } $cadena .= "<".$contenedor.$atributos_contenedor.">"; $cadena .= "<".$etiqueta.">"; //Agregamos lo de campo requerido, con constante ASTERISCO if (!(($arr["tag"] == "input")&&($arr["atributos"]["type"] == "hidden"))) { $cadena .= $arr["titulo"]; if ($arr["requerido"] && (($arr["tag"] == "textarea") || (($arr["tag"] == "input") && (($arr["atributos"]["type"] == "text") || ($arr["atributos"]["type"] == "password"))))){ $cadena .= ASTERISCO; } $cadena .= ": "; //Con <textare> y <select> de lista no desplegable, //agregamos un salto if (($arr["tag"] == "textarea") || (($arr["tipo"] == "menú") && ($arr["atributos"]["size"] > 1))) { $cadena .= "<br />"; } } //Cerramos la etiqueta $cadena .= "</".$etiqueta.">"; //Declaramos una array temporal para contener todos los elementos de un //tipo selector (<input type="radio">) o elementos <option> de un tipo menú //(elemento <select>). $tages = array(); if (($arr["tipo"] == "selector") || ($arr["tipo"] == "menú")){ foreach ($arr["opciones"] as $clave => $valor){ $tages[$clave] = $valor; } if ($arr["tipo"] == "menú"){ //Ponemos el tag <select> para elementos tipo "menú" $cadena .= "<select name=\"".$campo."\""; //Ponemos en el atributo title el tipo, requerido y longitud $cadena .= " title=\"".$arr["tipo"]; if ($arr["requerido"]) { $cadena .= SEPARA."requerido"; } else { $cadena .= SEPARA."opcional"; } $cadena .= SEPARA.$arr["longitud-min"].SEPARA.$arr["longitud-max"]."\"";; //Ponemos los atributos específicos para este campo $attr = $arr["atributos"]; foreach ($attr as $atributo => $valorAtributo){ $cadena .= " ".$atributo."=\"".$valorAtributo."\""; } $cadena .= ">"; } } else { //Si no es <input type="radio"> o <option> para <select>, sólo ponemos //un item en el array temporal $tages["sin"] = $arr["valor"]; } //Iteramos por el array temporal para construir elementos foreach ($tages as $titulo_r => $valor_r){ if ($arr["tipo"] == "menú"){ //Elementos <option> se tratan aparte $cadena .= "<".$arr["tag"]." value=\"".$valor_r."\""; if ($valor_r == $arr["valor"]){ $cadena .= " selected=\"selected\""; } $cadena .= ">".$titulo_r."</".$arr["tag"].">"; } else { //En elementos <input type="radio"> agregamos su título if ($arr["tipo"] == "selector"){ $cadena .= "<label><span>".$titulo_r.":</span>"; } //Ponemos el tag del campo $cadena .= "<".$arr["tag"]." name=\"".$campo."\""; //Ponemos en el atributo title el tipo, requerido y longitud $cadena .= " title=\"".$arr["tipo"]; if ($arr["requerido"]) { $cadena .= SEPARA."requerido"; } else { $cadena .= SEPARA."opcional"; } $cadena .= SEPARA.$arr["longitud"]."\""; //Ponemos los atributos específicos para este campo $attr = $arr["atributos"]; foreach ($attr as $atributo => $valorAtributo){ if (($atributo != "checked")|| (($atributo == "checked")&&($valorAtributo == "checked"))){ $cadena .= " ".$atributo."=\"".$valorAtributo."\""; } } //para los <input type="text"> agrega size y maxlength //según longitud if (($arr["tag"] == "input") && (($arr["atributos"]["type"] == "text")|| ($arr["atributos"]["type"] == "password"))){ $cadena .= " size=\"".$arr["longitud-max"]. "\" maxlength=\"".$arr["longitud-max"]."\""; } //para <input type="radio">, pone checked al seleccionado if (($arr["tipo"] == "selector") && ($arr["valor"] == $valor_r)) { $cadena .= " checked=\"checked\""; } //Ponemos el valor y cerramos el tag if ($arr["tag"] == "input"){ if ($arr["atributos"]["type"] == "password"){ //No devolvemos password al usuario cuando hay un error //de validación para evitar que fluya en exceso $cadena .= " value=\"\" />"; } else { $cadena .= " value=\"".$valor_r."\" />"; } } else { $cadena .= ">".$valor_r."</".$arr["tag"].">"; } //Para los <input type="radio"> añadimos separador if ($arr["tipo"] == "selector") { $cadena .= "</label>".$arr["separador-selector"]; } } } //Cerramos el <select> abierto si es un tipo "menú" if ($arr["tipo"] == "menú") $cadena .= "</select>"; //Cerramos el contenedor, un <label> o un <fieldset> $cadena .= "</".$contenedor.">"; //Si el contenedor es un <label> agregamos un salto de línea if ($contenedor == "label") $cadena .= "<br />"; } //Agregamos un pie de información sobre requeridos if (ASTERISCO != "") { $cadena .= "<div style=\"font-family: Arial Narrow\">". "Los campos marcados con ".ASTERISCO. " son requeridos.</div>"; } //Agregamos botones de submit (no reset). NOTA: El name y value son //importantes, porque se detectan en validar_form() cuando no está //activado Javascript. Si se modifica aquí, tenerlo en cuenta allí. $cadena .= "<input type=\"submit\" name=\"envio\" value=\"enviar\" />"; //Agregamos el mensaje de validar y cerramos form $cadena .= "<div style=\"color: red\">".$mensaje_validar."</div>"; $cadena .= "</form>"; //Devolvemos la cadena literal HTML return $cadena; } /* Esta función nos extrae los nombres de campos y valores a partir * del array de definición y valores $array_campos. Así el array retornado * podrá ser usado en procesos posteriores después de haber sido validado. */ function extraer_valores($array_campos){ $array_valores = array(); foreach ($array_campos as $campo => $arr){ $cadena = ""; if (($arr["tag"] == "input")&&($arr["atributos"]["type"] == "checkbox")){ if ($arr["atributos"]["checked"] == "checked"){ $cadena .= "si"; } else { $cadena .= "no"; } } else { $cadena .= $arr["valor"]; } $array_valores[$campo] = $cadena; } return $array_valores; } /* Esta función lista los campos y valores en un formato de presentación. */ function listar_campos($array_campos, $tag){ $cadena = "<".$tag.">"; foreach ($array_campos as $campo => $arr){ if (!(($arr["tag"] == "input")&&($arr["atributos"]["type"] == "hidden"))) { $cadena .= "<li>".$arr["titulo"].": <span style=\"color: blue\">"; if (($arr["tag"] == "input")&&($arr["atributos"]["type"] == "checkbox")){ if ($arr["atributos"]["checked"] == "checked"){ $cadena .= "si"; } else { $cadena .= "no"; } } else { $cadena .= $arr["valor"]; } $cadena .= "</span></li>"; } } return $cadena."</".$tag.">"; } ?>
valida-form.js
/* JavaScript para validar los campos de un formulario * Andrés de la Paz ©2010 * http://www.wextensible.com * * La validación de campos en el navegador es equivalente a la que se realiza * en el servidor con valida-form.php, usando el mismo conjunto de patrones * de expresiones regulares. Este array de patrones se encuentra en un * script a la cabecera del documento que porta el formulario. * * En PHP se usa la función valida_form(), pero aquí desglosamos el proceso * en dos partes. En primer lugar la función validaForm() se encarga * de extraer un campo de la matriz de controles del formulario. Si la clase * empieza por PREFIJOVAL será validado. Entonces obtenemos los atributos * name, value y title. Este último contiene el tipo de patrón, requerido y * las longitudes del campo. Con todo esto llamamos a la función para validar * validaCampo(campo, valor, titulo, tipo, requerido, longitudMin, longitudMax) * que devuelve una lista de mensajes de error o un "" si no hubieron. * Finalmente procesamos el mensaje y el envío del formulario en su caso. */ /* Este valor permite compararlo con el del atributo "class" para ver * si validamos o no el campo. */ var PREFIJOVAL = "val-"; /* Aquí validamos los campos del formulario, iterando por los elementos y * llamando a la función validaCampo(). Para ello disponemos en el * elemento <form> del atributo onsubmit="return validaForm(this)". */ function validaForm(formulario) { //Para concatenar mensajes var mensaje = ""; //En el documento hay una casilla de verificación por fuera del //formulario que nos sirve para probar a enviarlo sin que se //realice la validación con Javascript. Digamos que sería lo //mismo que si el usuario no tuviera activado Javascript. //Esto lo podemos suprimir en una versión definitiva. var nojs = document.getElementById("no-valida-js"); if (!nojs.checked){ //Obtenemos los campos de la matriz de elementos del formulario var campos = formulario.elements; //Iteramos por estos campos for (var i=0; i<campos.length; i++) { //Si la clase empieza por PREFIJOVAL no validamos var clase = campos[i].className; if (clase.substring(0,4) == PREFIJOVAL){ //Obtenemos nombre y valor del campo var campo = campos[i].name; var valor = campos[i].value; //El elementoTitulo es un <span> que está inmediatamente //antes del campo, por lo que podemos acceder mediante //previousSibling. Ahí está el título o etiqueta del //campo (no confundir con el atributo title que usamos //para portar el tipo, requerido y longitud). var titulo = ""; var elementoTitulo = campos[i].previousSibling; if ((elementoTitulo != null) && (elementoTitulo.nodeType == 1) && (elementoTitulo.tagName.toLowerCase() == "span")) { //La función getInnerText nos permite extraer el texto //del <span>. Es una función que unifica el distinto //comportamiento de navegadores. Se explica en este //enlace de este sitio titulo = getInnerText(elementoTitulo); //Quitamos el asterisco de requerido, dos puntos y espacios titulo = titulo.replace(/\*?\:[ \s]*/g, ""); } //Extraemos tipo, requerido y longitud del atributo title var tipo = "valor"; var requerido = false; var longitudMin = 0; var longitudMax = 0; var cadenaTitle = campos[i].title; if (cadenaTitle != ""){ var arr = cadenaTitle.split("#"); //Si no hay 4 entradas en el array, se quedan el tipo, requerido //y longitud declarados antes if (arr.length == 4){ tipo = arr[0]; var req = arr[1].toLowerCase(); if ((req == "requerido")||(req == "opcional")){ requerido = (req == "requerido"); } longitudMin = parseInt(arr[2]); longitudMax = parseInt(arr[3]); } } //validamos el campo mensaje += validaCampo(campo, valor, titulo, tipo, requerido, longitudMin, longitudMax); } } } //Si hay mensaje if (mensaje != "") { mensaje = "<p>Hay errores con la validación de este formulario:</p>" + "<ol>" + mensaje + "</ol>"; } //Esta parte sirve para alertar de que se está enviando el mensaje var mensajeEnvio = ""; var colorMensaje = "red"; if (mensaje == ""){ colorMensaje = "green"; formulario.envio.value = "Enviando..."; formulario.envio.disabled = true; mensajeEnvio = "Se enviarán los datos del formulario..."; } //El último elemento del formulario será un <div> donde vamos a //poner la lista de mensajes var mensajeForm = formulario.lastChild; mensajeForm.style.color = colorMensaje; mensajeForm.innerHTML = mensaje + mensajeEnvio; //Advertimos al usuario que se enviará el formulario dándole la //posibilidad de cancelar el envío if (mensaje == ""){ if (window.confirm("¿Enviar mensaje?")){ return true; } else { formulario.envio.value = "enviar"; formulario.envio.disabled = false; mensajeForm.innerHTML = "Envío cancelado"; return false; } } else { return false; } } /* Se valida un campo de formulario usando el array patronesTipos que está * en un script en la cabecera de la página. */ function validaCampo(campo, valor, titulo, tipo, requerido, longitud){ //Aquí pondremos el mensaje de error var mensaje = ""; //Mensaje de longitud var mensajeLongitud = "("; if (longitudMax == longitudMin){ mensajeLongitud += longitudMax; } else { mensajeLongitud += "de " + longitudMin + " a " + longitudMax; } mensajeLongitud += " caracteres)"; //Verificamos longitud if ((valor.length < longitudMin) || (valor.length > longitudMax)){ mensaje += "<li>El tamaño del valor del campo '" + titulo + "' es "; if (valor.length > longitudMax) { mensaje += "mayor"; } else { mensaje += "menor"; } mensaje += " que el esperado " + mensajeLongitud +".</li>"; } else { //Verificamos que no esté vacío si es requerido var estaVacio = (valor == null || valor.length == 0 || /^\s+$/.test(valor)) if (requerido && estaVacio) { mensaje = "<li>El valor del campo '" + titulo + "' es requerido " + mensajeLongitud + "</li>"; } else if ((tipo != "valor") && (tipo != "selector") && (tipo != "menú") && (requerido || (!requerido && !estaVacio))) { //Si es un tipo testamos la expresión regular var masMensaje = ""; var encontrado = false; for (var i=0; i<patronesTipos.length; i=i+3){ if (tipo == patronesTipos[i]){ //La funcion test de Javascript verifica el patrón, cuyo //resultado true o false ponemos en esta variable var validado = patronesTipos[i+1].test(valor); //Extraemos el mensaje de error if (!validado) masMensaje = patronesTipos[i+2]; encontrado = true; break; } } //Si el tipo no se encontró en el array de patrones if (!encontrado){ mensaje = "<li>Falta el tipo '" + tipo + "' para el " + "campo " + titulo + " " + mensajeLongitud + "</li>"; } else if (!validado){ //Si no se validó, formamos el mensaje de error mensaje = "<li>Error en el campo '" + titulo + "'. " + masMensaje + " " + mensajeLongitud + "</li>"; } } } //devolvemos "" o un mensaje de error return mensaje; }
form2.php
Este código es exactamente igual que form1.php, modificándose sólo en el nombre dado a la sesión y en la definición de los campos particulares para este formulario, que exponemos a continuación. Este ejemplo no acompaña las variables de configuración para ver el ejemplo en funcionamiento que veíamos al pie del ejemplo en form1.php.
... //Damos un nombre a estas sesiones e iniciamos sesión define("SESION", "ejemploValidaForm2"); session_name(SESION); ... //Esta constante da el nombre al campo identificador de formulario. define("IDENTIFICAFORM", "identifica-form2"); ... //Incluimos aquí el script que valida el formulario include "valida-form.php"; //Array con la definición y valores de campos $array_campos = array( IDENTIFICAFORM => array( "valor" => "", "titulo" => IDENTIFICAFORM, "tipo" => IDENTIFICAFORM, "longitud-min" => 32, "longitud-max" => 32, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "hidden", "class" => PREFIJOVAL ) ), "numero-natural" => array( "valor" => "0", "titulo" => "Número natural", "tipo" => "número natural", "longitud-min" => 1, "longitud-max" => 16, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "numero-entero" => array( "valor" => "0", "titulo" => "Número entero", "tipo" => "número entero", "longitud-min" => 1, "longitud-max" => 16, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "numero-real" => array( "valor" => "0.0", "titulo" => "Número real", "tipo" => "número real", "longitud-min" => 1, "longitud-max" => 32, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "numero-rango" => array( "valor" => "10", "titulo" => "Número de 10 a 20", "tipo" => "número natural", "longitud-min" => 2, "longitud-max" => 2, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL, "onblur" => "javascript: limitaValor(this, 'entero', 10, 20);" ) ), "nombre-apellidos" => array( "valor" => "", "titulo" => "Nombre y apellidos", "tipo" => "nombre de persona", "longitud-min" => 7, "longitud-max" => 35, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "user" => array( "valor" => "", "titulo" => "Usuario", "tipo" => "usuario", "longitud-min" => 4, "longitud-max" => 20, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "pwd" => array( "valor" => "", "titulo" => "Contraseña", "tipo" => "contraseña", "longitud-min" => 4, "longitud-max" => 10, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "password", "class" => PREFIJOVAL ) ), "dni" => array( "valor" => "", "titulo" => "D.N.I.", "tipo" => "DNI", "longitud-min" => 9, "longitud-max" => 9, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "telefono" => array( "valor" => "", "titulo" => "Teléfono", "tipo" => "teléfono", "longitud-min" => 9, "longitud-max" => 9, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "ver-email" => array( "valor" => "si", "titulo" => "Publicar e-mail", "tipo" => "verificación", "longitud-min" => 2, "longitud-max" => 2, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "checkbox", "class" => PREFIJOVAL, "checked" => "" ) ), "enviar-email" => array( "valor" => "si", "titulo" => "Enviar e-mail", "tipo" => "verificación", "longitud-min" => 2, "longitud-max" => 2, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "checkbox", "class" => PREFIJOVAL, "checked" => "checked" ) ), "email" => array( "valor" => "", "titulo" => "E-mail", "tipo" => "email", "longitud-min" => 8, "longitud-max" => 50, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "text", "class" => PREFIJOVAL ) ), "mensaje" => array( "valor" => "", "titulo" => "Mensaje", "tipo" => "texto simple", "longitud-min" => 1, "longitud-max" => 100, "requerido" => true, "tag" => "textarea", "atributos" => array( "cols" => "30", "rows" => "5", "class" => PREFIJOVAL ) ), "mensaje-2" => array( "valor" => "", "titulo" => "Mensaje con texto general", "tipo" => "texto Latín-B", "longitud-min" => 1, "longitud-max" => 150, "requerido" => true, "tag" => "textarea", "atributos" => array( "cols" => "45", "rows" => "5", "class" => PREFIJOVAL, "onkeypress" => "javascript: controlaTextarea(this, event, 150);" ) ), "navegador" => array( "valor" => "Sin especificar", "titulo" => "Navegador", "opciones" => array( "Sin especificar" => "Sin especificar", "Internet Explorer" => "Internet Explorer", "Firefox" => "Firefox", "Opera" => "Opera", "Safari" => "Safari", "Google Chrome" => "Chrome", "Otros" => "Otros" ), "tipo" => "menú", "longitud-min" => 1, "longitud-max" => 20, "requerido" => true, "tag" => "option", "atributos" => array( "class" => PREFIJOVAL, "size" => 1 ) ), "sistema-operativo" => array( "valor" => "Sin especificar", "titulo" => "Sistema Operativo", "opciones" => array( "Sin especificar" => "Sin especificar", "Windows" => "Windows", "Mac OS X" => "Mac OS X", "GNU/Linux" => "GNU/Linux", "Linux" => "Linux", "Otros" => "Otros" ), "tipo" => "selector", "estilo-fieldset" => "width: 20em", "clase-fieldset" => "", "separador-selector" => " ", "longitud-min" => 1, "longitud-max" => 20, "requerido" => true, "tag" => "input", "atributos" => array( "type" => "radio", "class" => PREFIJOVAL ) ) ); //Construimos el array de patrones para esta definición contruir_array_patrones($array_campos); ...